diff --git a/.github/workflows/ci-nix.yml b/.github/workflows/ci-nix.yml new file mode 100644 index 0000000..461c3d6 --- /dev/null +++ b/.github/workflows/ci-nix.yml @@ -0,0 +1,87 @@ +name: CI-nix + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + paths-ignore: + - 'doc/**' + pull_request: + paths-ignore: + - 'doc/**' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + compiler: [gcc, clang] + + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + # with: + # submodules: recursive + - name: Install dependencies + if: matrix.os != 'macos-latest' + run: | + sudo apt-get install tcl tcl-dev + + # - name: Install dependencies MacOS + # if: matrix.os == 'macos-latest' + # run: | + # sudo brew install tcl-devel + + - name: Pre-requirements + run: | + test -f ../tclconfig/tcl.m4 && echo "use ../tclconfig" || { + test -f tclconfig/tcl.m4 && echo "use ./tclconfig" || \ + echo 'checkout tclconfig module ...' && \ + git submodule update --init --recursive tclconfig && echo "tclconfig module is up-to-date" || { + echo 'checkout modules failed, clone ...' && \ + git clone https://github.com/tcltk/tclconfig.git ../tclconfig + } + } + - name: Software versions tcl/${{ matrix.compiler }} + run: | + echo "${{ matrix.COMPILER }}: $(${{ matrix.COMPILER }} --version)" + echo "TCL: $(echo puts [info patchlevel] | tclsh)" + + - name: Configure ${{ matrix.compiler }} + env: + CC: ${{ matrix.compiler }} + run: | + test -f configure && echo "configure already exists in $(pwd) ..." || autoreconf -f + mkdir ./unix/build-${{ matrix.compiler }}; cd "$_" + ../../configure --with-tcl=/usr/lib/tcl8.6 + + - name: Build ${{ matrix.compiler }} + working-directory: ./unix/build-${{ matrix.compiler }} + env: + CC: ${{ matrix.compiler }} + run: | + make -j4 + + - name: Test ${{ matrix.compiler }} + working-directory: ./unix/build-${{ matrix.compiler }} + run: | + echo $TZ; timedatectl status + # tclsh ../../tests/all.tcl + make test + + - name: Install ${{ matrix.compiler }} + working-directory: ./unix/build-${{ matrix.compiler }} + run: | + sudo make install + echo 'if {[catch {package require tclclockmod; clock format -now}]} {puts stderr "ERROR!"; exit 1} else {puts "OK."}' | tclsh + + - name: Clean ${{ matrix.compiler }} + working-directory: ./unix/build-${{ matrix.compiler }} + run: | + make clean + diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 01cfadd..347b482 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -25,19 +25,19 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - - name: Install dependencies + - name: Install dependencies run: | sudo apt-get update -qq sudo apt-get install -y tcl8.6-dev - - name: configure + - name: configure run: | autoconf autoreconf -iv cd unix ../configure --with-tcl=/usr/lib/tcl8.6 - - name: make + - name: make run: | cd unix make @@ -47,7 +47,8 @@ jobs: # current time and time-zone: echo $TZ; timedatectl status # load local library and execute local test cases: - tclsh tests/all.tcl + cd unix + make test - name: install run: | diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7fb2a58 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tclconfig"] + path = tclconfig + url = https://github.com/tcltk/tclconfig.git diff --git a/Makefile.in b/Makefile.in index 1a44f68..562005c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -279,6 +279,14 @@ $(PKG_STUB_LIB_FILE): $(PKG_STUB_OBJECTS) ${MAKE_STUB_LIB} $(RANLIB_STUB) $(PKG_STUB_LIB_FILE) +#======================================================================== + +gendate: + bison --output-file=$(srcdir)/generic/tclDate.c \ + --name-prefix=TclDate \ + --no-lines \ + $(srcdir)/generic/tclGetDate.y + #======================================================================== # We need to enumerate the list of .c to .o lines here. # diff --git a/README.md b/README.md index 32969c0..761e4ae 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ v.8.6.7-03 2018/12/03 -## TclClockMod: the fastest, most powerful Tcl clock engine written in C +## TclClockMod: the [fastest, most powerful](#performance-) Tcl clock engine written in C What is this ? ============== -This is the source distribution of the Tcl clock extension: the faster +This is the source distribution of the Tcl clock extension: the [faster](#performance-) Tcl-module for the replacement of the standard "clock" ensemble of tcl. You need to have your Tcl core compiled also. @@ -22,6 +22,8 @@ virtually anything you like with it, such as modifying it, redistributing it, and selling it either in whole or in part. See the "license.terms" file in the top-level distribution directory for complete information. +Now this clock-engine is a part of Tcl 8.7 / 9.0. + How to compile ? ---------------- @@ -52,6 +54,18 @@ Function | Performance increase | tclclockmod | tcl8.6-clock The difference is much more larger, if the tests are running multi-threaded with parasitic load. +#### How the performance is measured: + +Both tcl-core as well as tclclockmod has a file [tests-perf/clock.perf.tcl](./tests-perf/clock.perf.tcl) which can be used to compare the execution times of original clock and tclclockmod. It can be also simply performed from the tclsh, with and without loading of the module.
+Here is a diff illustrating that (which amounted to almost 95x speed-up): +```diff + % timerate -calibrate {} + % clock scan "" -timezone :CET; clock scan "" -gmt 1; # warming up + % timerate { clock scan "2009-06-30T18:30:00 CEST" -format "%Y-%m-%dT%H:%M:%S %z" -gmt 1 } +- 62.0972 µs/# 16094 # 16103.8 #/sec 999.392 net-ms ++ 0.654699 µs/# 1437085 # 1527419 #/sec 940.858 net-ms +``` + Tcl compatibility: ================= @@ -63,11 +77,9 @@ The module is currently usable with latest Tcl 8.6th version (>= 8.6.6), but can be used also with previous versions since 8.6.0 (note that some packages like "msgcat" should be upgraded in this case). +Since [TIP 688](https://core.tcl-lang.org/tips/doc/trunk/tip/688.md) (commits [GH/tcl/e736133f9c72](https://github.com/tcltk/tcl/commit/e736133f9c72a69186f1d6845b5fb52de03c23ab) or [CORE/tcl/7137ea11e9e343f6](https://core.tcl-lang.org/tcl/info/7137ea11e9e343f6)) this is a part of Tcl 8.7 / 9.0 and therefore fully compatible to newest core-tcl now, excepting few things (like `clock configure` -> `tcl::unsupported::clock::configure`). -Differences from base clock -=========================== - -clock unixtime +Differences from base clock: `clock unixtime` -------------- Same as "clock scan" except: diff --git a/configure.ac b/configure.ac index 0946df5..abbcfac 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl to configure the system for the local environment. # so you can encode the package version directly into the source files. #----------------------------------------------------------------------- -AC_INIT([tclclockmod], [8.6.709]) +AC_INIT([tclclockmod], [8.6.710]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/doc/clock.n b/doc/clock.n new file mode 100644 index 0000000..87c44d8 --- /dev/null +++ b/doc/clock.n @@ -0,0 +1,1014 @@ +'\" +'\" Generated from file './doc/clock.dt' by tcllib/doctools with format 'nroff' +'\" Copyright (c) 2004 Kevin B. Kenny . All rights reserved. +'\" +.TH "clock" n 8.5 Tcl "Tcl Built-In Commands" +.so man.macros +.BS +.SH NAME +clock \- Obtain and manipulate dates and times +.SH "SYNOPSIS" +package require \fBTcl 8.5\fR +.sp +\fBclock add\fR \fItimeVal\fR ?\fIcount unit...\fR? ?\fI\-option value\fR? +.sp +\fBclock clicks\fR ?\fI\-option\fR? +.sp +\fBclock format\fR \fItimeVal\fR ?\fI\-option value\fR...? +.sp +\fBclock microseconds\fR +.sp +\fBclock milliseconds\fR +.sp +\fBclock scan\fR \fIinputString\fR ?\fI\-option value\fR...? +.sp +\fBclock seconds\fR +.sp +.BE +.SH "DESCRIPTION" +.PP +The \fBclock\fR command performs several operations that obtain and +manipulate values that represent times. The command supports several +subcommands that determine what action is carried out by the command. +.TP +\fBclock add\fR \fItimeVal\fR ?\fIcount unit...\fR? ?\fI\-option value\fR? +Adds a (possibly negative) offset to a time that is expressed as an +integer number of seconds. See \fBCLOCK ARITHMETIC\fR for a full description. +.TP +\fBclock clicks\fR ?\fI\-option\fR? +If no \fI\-option\fR argument is supplied, returns a high-resolution +time value as a system-dependent integer value. The unit of the value +is system-dependent but should be the highest resolution clock available +on the system such as a CPU cycle counter. See \fBHIGH RESOLUTION TIMERS\fR for a full description. +.RS +.PP +If the \fI\-option\fR argument is \fB\-milliseconds\fR, then the command +is synonymous with \fBclock milliseconds\fR (see below). This +usage is obsolete, and \fBclock milliseconds\fR is to be +considered the preferred way of obtaining a count of milliseconds. +.PP +If the \fI\-option\fR argument is \fB\-microseconds\fR, then the command +is synonymous with \fBclock microseconds\fR (see below). This +usage is obsolete, and \fBclock microseconds\fR is to be +considered the preferred way of obtaining a count of microseconds. +.RE +.TP +\fBclock format\fR \fItimeVal\fR ?\fI\-option value\fR...? +Formats a time that is expressed as an integer number of seconds into a format +intended for consumption by users or external programs. +See \fBFORMATTING TIMES\fR for a full description. +.TP +\fBclock microseconds\fR +Returns the current time as an integer number of microseconds. See \fBHIGH RESOLUTION TIMERS\fR for a full description. +.TP +\fBclock milliseconds\fR +Returns the current time as an integer number of milliseconds. See \fBHIGH RESOLUTION TIMERS\fR for a full description. +.TP +\fBclock scan\fR \fIinputString\fR ?\fI\-option value\fR...? +Scans a time that is expressed as a character string and produces an +integer number of seconds. +See \fBSCANNING TIMES\fR for a full description. +.TP +\fBclock seconds\fR +Returns the current time as an integer number of seconds. +.SS "PARAMETERS" +.TP +\fIcount\fR +An integer representing a count of some unit of time. See +\fBCLOCK ARITHMETIC\fR for the details. +.TP +\fItimeVal\fR +An integer value passed to the \fBclock\fR command that represents an +absolute time as a number of seconds from the \fIepoch time\fR of +1 January 1970, 00:00 UTC. Note that the count of seconds does not +include any leap seconds; seconds are counted as if each UTC day has +exactly 86400 seconds. Tcl responds to leap seconds by speeding or +slowing its clock by a tiny fraction for some minutes until it is +back in sync with UTC; its data model does not represent minutes that +have 59 or 61 seconds. +.TP +\fInow\fR +Instead of \fItimeVal\fR a non-integer value \fBnow\fR can be used as +replacement for today, which is simply interpolated to the run-time as value +of \fBclock seconds\fR. For example: +.sp +\fBclock format now -f %a; # current day of the week\fR +.sp +\fBclock add now 1 month; # next month\fR +.TP +\fIunit\fR +One of the words, \fBseconds\fR, \fBminutes\fR, \fBhours\fR, +\fBdays\fR, \fBweeks\fR, \fBmonths\fR, or \fByears\fR, or +any unique prefix of such a word. Used in conjunction with \fIcount\fR +to identify an interval of time, for example, \fI3 seconds\fR or +\fI1 year\fR. +.SS "OPTIONS" +.TP +\fB\-base\fR time +Specifies that any relative times present in a \fBclock scan\fR command +are to be given relative to \fItime\fR. \fItime\fR must be expressed as +a count of nominal seconds from the epoch time of 1 January 1970, 00:00 UTC. +.TP +\fB\-format\fR format +Specifies the desired output format for \fBclock format\fR or the +expected input format for \fBclock scan\fR. The \fIformat\fR string consists +of any number of characters other than the per-cent sign +.PQ \fB%\fR +interspersed with any number of \fIformat groups\fR, which are two-character +sequences beginning with the per-cent sign. The permissible format groups, +and their interpretation, are described under \fBFORMAT GROUPS\fR. +.RS +.PP +On \fBclock format\fR, the default format is +.PP +.CS +%a %b %d %H:%M:%S %Z %Y +.CE +.PP +On \fBclock scan\fR, the lack of a \fB\-format\fR option indicates that a +.QW "free format scan" +is requested; see \fBFREE FORM SCAN\fR for a description of what happens. +.RE +.TP +\fB\-gmt\fR boolean +If \fIboolean\fR is true, specifies that a time specified to \fBclock add\fR, +\fBclock format\fR or \fBclock scan\fR should be processed in +UTC. If \fIboolean\fR is false, the processing defaults to the local time +zone. This usage is obsolete; the correct current usage is to +specify the UTC time zone with +.QW "\fB\-timezone\fR \fI:UTC\fR" +or any of the equivalent ways to specify it. +.TP +\fB\-locale\fR localeName +Specifies that locale-dependent scanning and formatting (and date arithmetic +for dates preceding the adoption of the Gregorian calendar) is to be done in +the locale identified by \fIlocaleName\fR. The locale name may be any of +the locales acceptable to the \fBmsgcat\fR package, or it may be the special +name \fIsystem\fR, which represents the current locale of the process, or +the null string, which represents Tcl's default locale. +.RS +.PP +The effect of locale on scanning and formatting is discussed in the +descriptions of the individual format groups under \fBFORMAT GROUPS\fR. +The effect of locale on clock arithmetic is discussed under +\fBCLOCK ARITHMETIC\fR. +.RE +.TP +\fB\-timezone\fR zoneName +Specifies that clock arithmetic, formatting, and scanning are to be done +according to the rules for the time zone specified by \fIzoneName\fR. +The permissible values, and their interpretation, are discussed under +\fBTIME ZONES\fR. +On subcommands that expect a \fB\-timezone\fR argument, the default +is to use the \fIcurrent time zone\fR. The current time zone is +determined, in order of preference, by: +.RS +.IP [1] +the environment variable \fBTCL_TZ\fR. +.IP [2] +the environment variable \fBTZ\fR. +.IP [3] +on Windows systems, the time zone settings from the Control Panel. +.RE +.PP +If none of these is present, the C \fBlocaltime\fR and \fBmktime\fR +functions are used to attempt to convert times between local and +Greenwich. On 32-bit systems, this approach is likely to have bugs, +particularly for times that lie outside the window (approximately the +years 1902 to 2037) that can be represented in a 32-bit integer. +.SH "CLOCK ARITHMETIC" +.PP +The \fBclock add\fR command performs clock arithmetic on a value +(expressed as nominal seconds from the epoch time of 1 January 1970, 00:00 UTC) +given as its first argument. The remaining arguments (other than the +possible \fB\-timezone\fR, \fB\-locale\fR and \fB\-gmt\fR options) +are integers and keywords in alternation, where the keywords are chosen +from \fBseconds\fR, \fBminutes\fR, \fBhours\fR, +\fBdays\fR, \fBweeks\fR, \fBmonths\fR, or \fByears\fR, or +any unique prefix of such a word. +.PP +Addition of seconds, minutes and hours is fairly straightforward; +the given time increment (times sixty for minutes, or 3600 for hours) +is simply added to the \fItimeVal\fR given +to the \fBclock add\fR command. The result is interpreted as +a nominal number of seconds from the Epoch. +.PP +Surprising results +may be obtained when crossing a point at which a leap second is +inserted or removed; the \fBclock add\fR command simply ignores +leap seconds and therefore assumes that times come in sequence, +23:59:58, 23:59:59, 00:00:00. (This assumption is handled by +the fact that Tcl's model of time reacts to leap seconds by speeding +or slowing the clock by a minuscule amount until Tcl's time +is back in step with the world. +.PP +The fact that adding and subtracting hours is defined in terms of +absolute time means that it will add fixed amounts of time in time zones +that observe summer time (Daylight Saving Time). For example, +the following code sets the value of \fBx\fR to \fB04:00:00\fR because +the clock has changed in the interval in question. +.PP +.CS +set s [\fBclock scan\fR {2004-10-30 05:00:00} \e + -format {%Y-%m-%d %H:%M:%S} \e + -timezone :America/New_York] +set a [\fBclock add\fR $s 24 hours -timezone :America/New_York] +set x [\fBclock format\fR $a \e + -format {%H:%M:%S} -timezone :America/New_York] +.CE +.PP +Adding and subtracting days and weeks is accomplished by converting +the given time to a calendar day and time of day in the appropriate +time zone and locale. The requisite number of days (weeks are converted +to days by multiplying by seven) is added to the calendar day, and +the date and time are then converted back to a count of seconds from +the epoch time. +.PP +Adding and subtracting a given number of days across the point that +the time changes at the start or end of summer time (Daylight Saving Time) +results in the \fIsame local time\fR on the day in question. For +instance, the following code sets the value of \fBx\fR to \fB05:00:00\fR. +.PP +.CS +set s [\fBclock scan\fR {2004-10-30 05:00:00} \e + -format {%Y-%m-%d %H:%M:%S} \e + -timezone :America/New_York] +set a [\fBclock add\fR $s 1 day -timezone :America/New_York] +set x [\fBclock format\fR $a \e + -format {%H:%M:%S} -timezone :America/New_York] +.CE +.PP +In cases of ambiguity, where the same local time happens twice +on the same day, the earlier time is used. In cases where the conversion +yields an impossible time (for instance, 02:30 during the Spring +Daylight Saving Time change using US rules), the time is converted +as if the clock had not changed. Thus, the following code +will set the value of \fBx\fR to \fB03:30:00\fR. +.PP +.CS +set s [\fBclock scan\fR {2004-04-03 02:30:00} \e + -format {%Y-%m-%d %H:%M:%S} \e + -timezone :America/New_York] +set a [\fBclock add\fR $s 1 day -timezone :America/New_York] +set x [\fBclock format\fR $a \e + -format {%H:%M:%S} -timezone :America/New_York] +.CE +.PP +Adding a given number of days or weeks works correctly across the conversion +between the Julian and Gregorian calendars; the omitted days are skipped. +The following code sets \fBz\fR to \fB1752-09-14\fR. +.PP +.CS +set x [\fBclock scan\fR 1752-09-02 -format %Y-%m-%d -locale en_US] +set y [\fBclock add\fR $x 1 day -locale en_US] +set z [\fBclock format\fR $y -format %Y-%m-%d -locale en_US] +.CE +.PP +In the bizarre case that adding the given number of days yields a date +that does not exist because it falls within the dropped days of the +Julian-to-Gregorian conversion, the date is converted as if it was +on the Julian calendar. +.PP +Adding a number of months, or a number of years, is similar; it +converts the given time to a calendar date and time of day. It then +adds the requisite number of months or years, and reconverts the resulting +date and time of day to an absolute time. +.PP +If the resulting date is impossible because the month has too few days +(for example, when adding 1 month to 31 January), the last day of the +month is substituted. Thus, adding 1 month to 31 January will result in +28 February in a common year or 29 February in a leap year. +.PP +The rules for handling anomalies relating to summer time and to the +Gregorian calendar are the same when adding/subtracting months and +years as they are when adding/subtracting days and weeks. +.PP +If multiple \fIcount unit\fR pairs are present on the command, they +are evaluated consecutively, from left to right. +.SH "HIGH RESOLUTION TIMERS" +.PP +Most of the subcommands supported by the \fBclock\fR command deal with +times represented as a count of seconds from the epoch time, and this is the +representation that \fBclock seconds\fR returns. There are three exceptions, +which are all intended for use where higher-resolution times are required. +\fBclock milliseconds\fR returns the count of milliseconds from the +epoch time, and \fBclock microseconds\fR returns the count of microseconds +from the epoch time. In addition, there is a \fBclock clicks\fR command +that returns a platform-dependent high-resolution timer. Unlike +\fBclock seconds\fR and \fBclock milliseconds\fR, the value +of \fBclock clicks\fR is not guaranteed to be tied to any fixed +epoch; it is simply intended to be the most precise interval timer +available, and is intended only for relative timing studies such as +benchmarks. +.SH "FORMATTING TIMES" +.PP +The \fBclock format\fR command produces times for display to a user +or writing to an external medium. The command accepts times that are +expressed in seconds from the epoch time of 1 January 1970, 00:00 UTC, +as returned by \fBclock seconds\fR, \fBclock scan\fR, \fBclock add\fR, +\fBfile atime\fR or \fBfile mtime\fR. +.PP +If a \fB\-format\fR option is present, the following argument is +a string that specifies how the date and time are to be formatted. +The string consists +of any number of characters other than the per-cent sign +.PQ \fB%\fR +interspersed with any number of \fIformat groups\fR, which are two-character +sequences beginning with the per-cent sign. The permissible format groups, +and their interpretation, are described under \fBFORMAT GROUPS\fR. +.PP +If a \fB\-timezone\fR option is present, the following +argument is a string that specifies the time zone in which the date and time +are to be formatted. As an alternative to +.QW "\fB\-timezone\fR \fI:UTC\fR" , +the obsolete usage +.QW "\fB\-gmt\fR \fItrue\fR" +may be used. See +\fBTIME ZONES\fR for the permissible variants for the time zone. +.PP +If a \fB\-locale\fR option is present, the following argument is +a string that specifies the locale in which the time is to be formatted, +in the same format that is used for the \fBmsgcat\fR package. Note +that the default, if \fB\-locale\fR is not specified, is the root locale +\fB{}\fR rather than the current locale. The current locale may +be obtained by using \fB\-locale\fR \fBcurrent\fR. +In addition, some platforms support a \fBsystem\fR locale that +reflects the user's current choices. For instance, on Windows, the +format that the user has selected from dates and times in the Control +Panel can be obtained by using the \fBsystem\fR locale. On +platforms that do not define a user selection of date and time formats +separate from \fBLC_TIME\fR, \fB\-locale\fR \fBsystem\fR is +synonymous with \fB\-locale\fR \fBcurrent\fR. +.SH "SCANNING TIMES" +.PP +The \fBclock scan\fR command accepts times that are formatted as +strings and converts them to counts of seconds from the epoch time +of 1 January 1970, 00:00 UTC. It normally takes a \fB\-format\fR +option that is followed by a string describing +the expected format of the input. (See +\fBFREE FORM SCAN\fR for the effect of \fBclock scan\fR +without such an argument.) The string consists of any number of +characters other than the per-cent sign +.PQ \fB%\fR "" , +interspersed with any number of \fIformat groups\fR, which are two-character +sequences beginning with the per-cent sign. The permissible format groups, +and their interpretation, are described under \fBFORMAT GROUPS\fR. +.PP +If a \fB\-timezone\fR option is present, the following +argument is a string that specifies the time zone in which the date and time +are to be interpreted. As an alternative to \fB\-timezone\fR \fI:UTC\fR, +the obsolete usage \fB\-gmt\fR \fItrue\fR may be used. See +\fBTIME ZONES\fR for the permissible variants for the time zone. +.PP +If a \fB\-locale\fR option is present, the following argument is +a string that specifies the locale in which the time is to be interpreted, +in the same format that is used for the \fBmsgcat\fR package. Note +that the default, if \fB\-locale\fR is not specified, is the root locale +\fB{}\fR rather than the current locale. The current locale may +be obtained by using \fB\-locale\fR \fBcurrent\fR. +In addition, some platforms support a \fBsystem\fR locale that +reflects the user's current choices. For instance, on Windows, the +format that the user has selected from dates and times in the Control +Panel can be obtained by using the \fBsystem\fR locale. On +platforms that do not define a user selection of date and time formats +separate from \fBLC_TIME\fR, \fB\-locale\fR \fBsystem\fR is +synonymous with \fB\-locale\fR \fBcurrent\fR. +.PP +If a \fB\-base\fR option is present, the following argument is +a time (expressed in seconds from the epoch time) that is used as +a \fIbase time\fR for interpreting relative times. If no +\fB\-base\fR option is present, the base time is the current time. +.PP +Scanning of times in fixed format works by determining three things: +the date, the time of day, and the time zone. These three are then +combined into a point in time, which is returned as the number of seconds +from the epoch. +.PP +Before scanning begins, the format string is preprocessed +to replace \fB%c\fR, \fB%Ec\fR, \fB%x\fR, \fB%Ex\fR, +\fB%X\fR. \fB%Ex\fR, \fB%r\fR, \fB%R\fR, \fB%T\fR, +\fB%D\fR, \fB%EY\fR and \fB%+\fR format groups with counterparts +that are appropriate to the current locale and contain none of the +above groups. For instance, \fB%D\fR will (in the \fBen_US\fR locale) +be replaced with \fB%m/%d/%Y\fR. +.PP +The date is determined according to the fields that are present in the +preprocessed format string. In order of preference: +.IP [1] +If the string contains a \fB%s\fR format group, representing +seconds from the epoch, that group is used to determine the date. +.IP [2] +If the string contains a \fB%J\fR, \fB%EJ\fR or \fB%Ej\fR format groups, +representing the Calendar or Astronomical Julian Day Number, that groups +are used to determine the date. +Note, that in case of \fB%EJ\fR or \fB%Ej\fR format groups, representing +the Julian Date with time fraction, this groups may be used to determine +the date and time. +.IP [3] +If the string contains a complete set of format groups specifying +century, year, month, and day of month; century, year, and day of year; +or ISO8601 fiscal year, week of year, and day of week; those groups are +combined and used to determine the date. If more than one complete +set is present, the one at the rightmost position in the string is +used. +.IP [4] +If the string lacks a century but contains a set of format +groups specifying year of century, month and day of month; year of +century and day of year; or two-digit ISO8601 fiscal year, week of year, +and day of week; those groups are +combined and used to determine the date. If more than one complete +set is present, the one at the rightmost position in the string is +used. The year is presumed to lie in the range 1938 to 2037 inclusive. +.IP [5] +If the string entirely lacks any specification for the year +(or contains the year only on the locale's alternative calendar) +and contains a set of format groups specifying month and day of month, +day of year, or week of year and day of week, those groups are +combined and used to determine the date. If more than one complete +set is present, the one at the rightmost position in the string is +used. The year is determined by interpreting the base time in the given +time zone. +.IP [6] +If the string contains none of the above sets, but has a day +of the month or day of the week, the day of the month or day of the week +are used to determine the date by interpreting the base time in the +given time zone and returning the given day of the current week or month. +(The week runs from Monday to Sunday, ISO8601-fashion.) If both day +of month and day of week are present, the day of the month takes +priority. +.IP [7] +If none of the above rules results in a usable date, the date +of the base time in the given time zone is used. +.PP +The time is also determined according to the fields that are present in the +preprocessed format string. In order of preference: +.IP [1] +If the string contains a \fB%s\fR format group, representing +seconds from the epoch, that group determines the time of day. +.IP [2] +If the string contains either an hour on the 24-hour clock +or an hour on the 12-hour clock plus an AM/PM indicator, that hour determines +the hour of the day. If the string further contains a group specifying +the minute of the hour, that group combines with the hour. If the string +further contains a group specifying the second of the minute, that group +combines with the hour and minute. +.IP [3] +If the string contains neither a \fB%s\fR format group nor +a group specifying the hour of the day, then midnight (\fB00:00\fR, the start +of the given date) is used. +The time zone is determined by either the \fB\-timezone\fR or \fB\-gmt\fR +options, or by using the current time zone. +.PP +If a format string lacks a \fB%z\fR or \fB%Z\fR format group, +it is possible for the time to be ambiguous because it appears twice +in the same day, once without and once with Daylight Saving Time. +If this situation occurs, the first occurrence of the time is chosen. +(For this reason, it is wise to have the input string contain the +time zone when converting local times. This caveat does not apply to +UTC times.) +.PP +If the interpretation of the groups yields an impossible time because +a field is out of range, enough of that field's unit will be added to +or subtracted from the time to bring it in range. Thus, if attempting to +scan or format day 0 of the month, one day will be subtracted from day +1 of the month, yielding the last day of the previous month. +.PP +If the interpretation of the groups yields an impossible time because +a Daylight Saving Time change skips over that time, or an ambiguous +time because a Daylight Saving Time change skips back so that the clock +observes the given time twice, and no time zone specifier (\fB%z\fR +or \fB%Z\fR) is present in the format, the time is interpreted as +if the clock had not changed. +.SH "FORMAT GROUPS" +.PP +The following format groups are recognized by the \fBclock scan\fR and +\fBclock format\fR commands. +.TP +\fB%a\fR +On output, receives an abbreviation (\fIe.g.,\fR \fBMon\fR) for the day +of the week in the given locale. On input, matches the name of the day +of the week in the given locale (in either abbreviated or full form, or +any unique prefix of either form). +.TP +\fB%A\fR +On output, receives the full name (\fIe.g.,\fR \fBMonday\fR) of the day +of the week in the given locale. On input, matches the name of the day +of the week in the given locale (in either abbreviated or full form, or +any unique prefix of either form). +.TP +\fB%b\fR +On output, receives an abbreviation (\fIe.g.,\fR \fBJan\fR) for the name +of the month in the given locale. On input, matches the name of the month +in the given locale (in either abbreviated or full form, or +any unique prefix of either form). +.TP +\fB%B\fR +On output, receives the full name (\fIe.g.,\fR \fBJanuary\fR) +of the month in the given locale. On input, matches the name of the month +in the given locale (in either abbreviated or full form, or +any unique prefix of either form). +.TP +\fB%c\fR +On output, receives a localized representation of date and time of day; +the localized representation is expected to use the Gregorian calendar. +On input, matches whatever \fB%c\fR produces. +.TP +\fB%C\fR +On output, receives the number of the century in Indo-Arabic numerals. +On input, matches one or two digits, possibly with leading whitespace, +that are expected to be the number of the century. +.TP +\fB%d\fR +On output, produces the number of the day of the month, as two decimal +digits. On input, matches one or two digits, possibly with leading +whitespace, that are expected to be the number of the day of the month. +.TP +\fB%D\fR +This format group is synonymous with \fB%m/%d/%Y\fR. It should be +used only in exchanging data within the \fBen_US\fR locale, since +other locales typically do not use this order for the fields of the date. +.TP +\fB%e\fR +On output, produces the number of the day of the month, as one or +two decimal digits (with a leading blank for one-digit dates). +On input, matches one or two digits, possibly with leading +whitespace, that are expected to be the number of the day of the month. +.TP +\fB%Ec\fR +On output, produces a locale-dependent representation of the date and +time of day in the locale's alternative calendar. On input, matches +whatever \fB%Ec\fR produces. The locale's alternative calendar need not +be the Gregorian calendar. +.TP +\fB%EC\fR +On output, produces a locale-dependent name of an era in the locale's +alternative calendar. On input, matches the name of the era or any +unique prefix. +.TP +\fB%EE\fR +On output, produces the string \fBB.C.E.\fR or \fBC.E.\fR, or a +string of the same meaning in the locale, to indicate whether \fB%Y\fR refers +to years before or after Year 1 of the Common Era. On input, accepts +the string \fBB.C.E.\fR, \fBB.C.\fR, \fBC.E.\fR, \fBA.D.\fR, or the +abbreviation appropriate to the current locale, and uses it to fix +whether \fB%Y\fR refers to years before or after Year 1 of the +Common Era. +.TP +\fB%Ej\fR +On output, produces a string of digits giving the Astronomical Julian Date or +Astronomical Julian Day Number (JDN/JD). In opposite to calendar julian day +\fB%J\fR, it starts the day at noon. +On input, accepts a string of digits (or floating point with the time fraction) +and interprets it as an Astronomical Julian Day Number (JDN/JD). +The Astronomical Julian Date is a count of the number of calendar days +that have elapsed since 1 January, 4713 BCE of the proleptic +Julian calendar, which contains also the time fraktion (after floating point). +The epoch time of 1 January 1970 corresponds to Astronomical JDN 2440587.5. +This value corresponds the julian day used in sqlite-database, and is the same +as result of \fBselect julianday(:seconds, 'unixepoch')\fR. +.TP +\fB%EJ\fR +On output, produces a string of digits giving the Calendar Julian Date. +In opposite to julian day \fB%J\fR format group, it produces float number. +In opposite to astronomical julian day \fB%Ej\fR group, it starts at midnight. +On input, accepts a string of digits (or floating point with the time fraction) +and interprets it as a Calendar Julian Day Number. +The Calendar Julian Date is a count of the number of calendar days +that have elapsed since 1 January, 4713 BCE of the proleptic +Julian calendar, which contains also the time fraktion (after floating point). +The epoch time of 1 January 1970 corresponds to Astronomical JDN 2440588. +.TP +\fB%Es\fR +This affects similar to \fB%s\fR, but in opposition to \fB%s\fR it parses +or formats local seconds (not the posix seconds). +Because \fB%s\fR has the same precedence as \fB%s\fR (uniquely determines +a point in time), it overrides all other input formats. +.TP +\fB%Ex\fR +On output, produces a locale-dependent representation of the date +in the locale's alternative calendar. On input, matches +whatever \fB%Ex\fR produces. The locale's alternative calendar need not +be the Gregorian calendar. +.TP +\fB%EX\fR +On output, produces a locale-dependent representation of the +time of day in the locale's alternative numerals. On input, matches +whatever \fB%EX\fR produces. +.TP +\fB%Ey\fR +On output, produces a locale-dependent number of the year of the era +in the locale's alternative calendar and numerals. On input, matches +such a number. +.TP +\fB%EY\fR +On output, produces a representation of the year in the locale's +alternative calendar and numerals. On input, matches what \fB%EY\fR +produces. Often synonymous with \fB%EC%Ey\fR. +.TP +\fB%g\fR +On output, produces a two-digit year number suitable for use with +the week-based ISO8601 calendar; that is, the year number corresponds +to the week number produced by \fB%V\fR. On input, accepts such +a two-digit year number, possibly with leading whitespace. +.TP +\fB%G\fR +On output, produces a four-digit year number suitable for use with +the week-based ISO8601 calendar; that is, the year number corresponds +to the week number produced by \fB%V\fR. On input, accepts such +a four-digit year number, possibly with leading whitespace. +.TP +\fB%h\fR +This format group is synonymous with \fB%b\fR. +.TP +\fB%H\fR +On output, produces a two-digit number giving the hour of the day +(00-23) on a 24-hour clock. On input, accepts such a number. +.TP +\fB%I\fR +On output, produces a two-digit number giving the hour of the day +(12-11) on a 12-hour clock. On input, accepts such a number. +.TP +\fB%j\fR +On output, produces a three-digit number giving the day of the year +(001-366). On input, accepts such a number. +.TP +\fB%J\fR +On output, produces a string of digits giving the calendar Julian Day Number. +On input, accepts a string of digits and interprets it as a Julian Day Number. +The Julian Day Number is a count of the number of calendar days +that have elapsed since 1 January, 4713 BCE of the proleptic +Julian calendar. The epoch time of 1 January 1970 corresponds +to Julian Day Number 2440588. +.TP +\fB%k\fR +On output, produces a one- or two-digit number giving the hour of the day +(0-23) on a 24-hour clock. On input, accepts such a number. +.TP +\fB%l\fR +On output, produces a one- or two-digit number giving the hour of the day +(12-11) on a 12-hour clock. On input, accepts such a number. +.TP +\fB%m\fR +On output, produces the number of the month (01-12) with exactly two +digits. On input, accepts two digits and interprets them as the number +of the month. +.TP +\fB%M\fR +On output, produces the number of the minute of the hour (00-59) +with exactly two digits. On input, accepts two digits and interprets them +as the number of the minute of the hour. +.TP +\fB%N\fR +On output, produces the number of the month (1-12) with one or two digits, +and a leading blank for one-digit dates. +On input, accepts one or two digits, possibly with leading whitespace, +and interprets them as the number of the month. +.TP +\fB%Od\fR, \fB%Oe\fR, \fB%OH\fR, \fB%OI\fR, \fB%Ok\fR, \fB%Ol\fR, \fB%Om\fR, \fB%OM\fR, \fB%OS\fR, \fB%Ou\fR, \fB%Ow\fR, \fB%Oy\fR +All of these format groups are synonymous with their counterparts +without the +.QW \fBO\fR , +except that the string is produced and parsed in the +locale-dependent alternative numerals. +.TP +\fB%p\fR +On output, produces an indicator for the part of the day, \fBAM\fR +or \fBPM\fR, appropriate to the given locale. If the script of the +given locale supports multiple letterforms, lowercase is preferred. +On input, matches the representation \fBAM\fR or \fBPM\fR in +the given locale, in either case. +.TP +\fB%P\fR +On output, produces an indicator for the part of the day, \fBam\fR +or \fBpm\fR, appropriate to the given locale. If the script of the +given locale supports multiple letterforms, uppercase is preferred. +On input, matches the representation \fBAM\fR or \fBPM\fR in +the given locale, in either case. +.TP +\fB%Q\fR +This format group is reserved for internal use within the Tcl library. +.TP +\fB%r\fR +On output, produces a locale-dependent time of day representation on a +12-hour clock. On input, accepts whatever \fB%r\fR produces. +.TP +\fB%R\fR +On output, the time in 24-hour notation (%H:%M). For a version +including the seconds, see \fB%T\fR below. On input, accepts whatever +\fB%R\fR produces. +.TP +\fB%s\fR +On output, simply formats the \fItimeVal\fR argument as a decimal +integer and inserts it into the output string. On input, accepts +a decimal integer and uses is as the time value without any further +processing. Since \fB%s\fR uniquely determines a point in time, it +overrides all other input formats. +.TP +\fB%S\fR +On output, produces a two-digit number of the second of the minute +(00-59). On input, accepts two digits and uses them as the second of the +minute. +.TP +\fB%t\fR +On output, produces a TAB character. On input, matches a TAB character. +.TP +\fB%T\fR +Synonymous with \fB%H:%M:%S\fR. +.TP +\fB%u\fR +On output, produces the number of the day of the week +(\fB1\fR\(->Monday, \fB7\fR\(->Sunday). On input, accepts a single digit and +interprets it as the day of the week. Sunday may be either \fB0\fR or +\fB7\fR. +.TP +\fB%U\fR +On output, produces the ordinal number of the week of the year +(00-53). The first Sunday of the year is the first day of week 01. On +input accepts two digits which are otherwise ignored. This format +group is never used in determining an input date. This interpretation +of the week of the year was once common in US banking but is now +largely obsolete. See \fB%V\fR for the ISO8601 week number. +.TP +\fB%V\fR +On output, produces the number of the ISO8601 week as a two digit +number (01-53). Week 01 is the week containing January 4; or the first +week of the year containing at least 4 days; or the week containing +the first Thursday of the year (the three statements are +equivalent). Each week begins on a Monday. On input, accepts the +ISO8601 week number. +.TP +\fB%w\fR +On output, produces the ordinal number of the day of the week +(Sunday==0; Saturday==6). On input, accepts a single digit and +interprets it as the day of the week; Sunday may be represented as +either 0 or 7. Note that \fB%w\fR is not the ISO8601 weekday number, +which is produced and accepted by \fB%u\fR. +.TP +\fB%W\fR +On output, produces a week number (00-53) within the year; week 01 +begins on the first Monday of the year. On input, accepts two digits, +which are otherwise ignored. This format group is never used in +determining an input date. It is not the ISO8601 week number; that +week is produced and accepted by \fB%V\fR. +.TP +\fB%x\fR +On output, produces the date in a locale-dependent representation. On +input, accepts whatever \fB%x\fR produces and is used to determine +calendar date. +.TP +\fB%X\fR +On output, produces the time of day in a locale-dependent +representation. On input, accepts whatever \fB%X\fR produces and is used +to determine time of day. +.TP +\fB%y\fR +On output, produces the two-digit year of the century. On input, +accepts two digits, and is used to determine calendar date. The +date is presumed to lie between 1938 and 2037 inclusive. Note +that \fB%y\fR does not yield a year appropriate for use with the ISO8601 +week number \fB%V\fR; programs should use \fB%g\fR for that purpose. +.TP +\fB%Y\fR +On output, produces the four-digit calendar year. On input, +accepts four digits and may be used to determine calendar date. Note +that \fB%Y\fR does not yield a year appropriate for use with the ISO8601 +week number \fB%V\fR; programs should use \fB%G\fR for that purpose. +.TP +\fB%z\fR +On output, produces the current time zone, expressed in hours and +minutes east (+hhmm) or west (\-hhmm) of Greenwich. On input, accepts a +time zone specifier (see \fBTIME ZONES\fR below) that will be used to +determine the time zone (this token is optionally applicable on input, +so the value is not mandatory and can be missing in input). +.TP +\fB%Z\fR +On output, produces the current time zone's name, possibly +translated to the given locale. On input, accepts a time zone +specifier (see \fBTIME ZONES\fR below) that will be used to determine the +time zone (token is also like \fB%z\fR optionally applicable on input). +This option should, in general, be used on input only when +parsing RFC822 dates. Other uses are fraught with ambiguity; for +instance, the string \fBBST\fR may represent British Summer Time or +Brazilian Standard Time. It is recommended that date/time strings for +use by computers use numeric time zones instead. +.TP +\fB%%\fR +On output, produces a literal +.QW \fB%\fR +character. On input, matches a literal +.QW \fB%\fR +character. +.TP +\fB%+\fR +Synonymous with +.QW "\fB%a %b %e %H:%M:%S %Z %Y\fR" . +.SH "TIME ZONES" +.PP +When the \fBclock\fR command is processing a local time, it has several +possible sources for the time zone to use. In order of preference, they +are: +.IP [1] +A time zone specified inside a string being parsed and matched by a \fB%z\fR +or \fB%Z\fR format group. +.IP [2] +A time zone specified with the \fB\-timezone\fR option to the \fBclock\fR +command (or, equivalently, by \fB\-gmt\fR \fB1\fR). +.IP [3] +A time zone specified in an environment variable \fBTCL_TZ\fR. +.IP [4] +A time zone specified in an environment variable \fBTZ\fR. +.IP [5] +The local time zone from the Control Panel on Windows systems. +.IP [6] +The C library's idea of the local time zone, as defined by the +\fBmktime\fR and \fBlocaltime\fR functions. +.PP +In case [1] \fIonly,\fR the string is tested to see if it is one +of the strings: +.PP +.CS + gmt ut utc bst wet wat at + nft nst ndt ast adt est edt + cst cdt mst mdt pst pdt yst + ydt hst hdt cat ahst nt idlw + cet cest met mewt mest swt sst + eet eest bt it zp4 zp5 ist + zp6 wast wadt jt cct jst cast + cadt east eadt gst nzt nzst nzdt + idle +.CE +.PP +If it is a string in the above list, it designates a known +time zone, and is interpreted as such. +.PP +For time zones in case [1] that do not match any of the above strings, +and always for cases [2]-[6], the following rules apply. +.PP +If the time zone begins with a colon, it is one of a +standardized list of names like \fB:America/New_York\fR +that give the rules for various locales. A complete list +of the location names is too lengthy to be listed here. +On most Tcl installations, the definitions of the locations +are to be found in named files in the directory +.QW "\fI/no_backup/tools/lib/tcl8.5/clock/tzdata\fR" . +On some Unix systems, these files are omitted, and the definitions are +instead obtained from system files in +.QW "\fI/usr/share/zoneinfo\fR" , +.QW "\fI/usr/share/lib/zoneinfo\fR" +or +.QW "\fI/usr/local/etc/zoneinfo\fR" . +As a special case, the name \fB:localtime\fR refers to +the local time zone as defined by the C library. +.PP +A time zone string consisting of a plus or minus sign followed by +four or six decimal digits is interpreted as an offset in +hours, minutes, and seconds (if six digits are present) from +UTC. The plus sign denotes a sign east of Greenwich; +the minus sign one west of Greenwich. +.PP +A time zone string conforming to the Posix specification of the \fBTZ\fR +environment variable will be recognized. The specification +may be found at +\fIhttp://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html\fR. +.PP +If the Posix time zone string contains a DST (Daylight Savings Time) +part, but doesn't contain a rule stating when DST starts or ends, +then default rules are used. For Timezones with an offset between 0 +and +12, the current European/Russian rules are used, otherwise the +current US rules are used. In Europe (offset +0 to +2) the switch +to summertime is done each last Sunday in March at 1:00 GMT, and +the switch back is each last Sunday in October at 2:00 GMT. In +Russia (offset +3 to +12), the switch dates are the same, only +the switch to summertime is at 2:00 local time, and the switch +back is at 3:00 local time in all time zones. The US switch to +summertime takes place each second Sunday in March at 2:00 local +time, and the switch back is each first Sunday in November at +3:00 local time. These default rules mean that in all European, +Russian and US (or compatible) time zones, DST calculations will +be correct for dates in 2007 and later, unless in the future the +rules change again. +.PP +Any other time zone string is processed by prefixing a colon and attempting +to use it as a location name, as above. +.SH "LOCALIZATION" +.PP +Developers wishing to localize the date and time formatting and parsing +are referred to \fIhttp://tip.tcl.tk/173\fR for a +specification. +.SH "FREE FORM SCAN" +.PP +If the \fBclock scan\fR command is invoked without a \fB\-format\fR +option, then it requests a \fIfree-form scan.\fR \fI +This form of scan is deprecated.\fR The reason for the deprecation +is that there are too many ambiguities. (Does the string +.QW 2000 +represent a year, a time of day, or a quantity?) No set of rules +for interpreting free-form dates and times has been found to +give unsurprising results in all cases. +.PP +If free-form scan is used, only the \fB\-base\fR and \fB\-gmt\fR +options are accepted. The \fB\-timezone\fR and \fB\-locale\fR +options will result in an error if \fB\-format\fR is not supplied. +.PP +For the benefit of users who need to understand legacy code that +uses free-form scan, the documentation for how free-form scan +interprets a string is included here: +.PP +If only a time is +specified, the current date is assumed. If the \fIinputString\fR +does not contain a +time zone mnemonic, the local time zone is assumed, unless the \fB\-gmt\fR +argument is true, in which case the clock value is calculated assuming +that the specified time is relative to Greenwich Mean Time. +\fB\-gmt\fR, if specified, affects only the computed time value; it does not +impact the interpretation of \fB\-base\fR. +.PP +If the \fB\-base\fR flag is specified, the next argument should contain +an integer clock value. Only the date in this value is used, not the +time. This is useful for determining the time on a specific day or +doing other date-relative conversions. +.PP +The \fIinputString\fR argument consists of zero or more specifications of the +following form: +.TP +\fItime\fR +A time of day, which is of the form: \fBhh?:mm?:ss?? ?meridian? ?zone?\fR +or \fBhhmm ?meridian? ?zone?\fR +If no meridian is specified, \fBhh\fR is interpreted on +a 24-hour clock. +.TP +\fIdate\fR +A specific month and day with optional year. The +acceptable formats are +.QW "\fBmm/dd\fR?\fB/yy\fR?" , +.QW "\fBmonthname dd\fR?\fB, yy\fR?" , +.QW "\fBday, dd monthname \fR?\fByy\fR?" , +.QW "\fBdd monthname yy\fR" , +.QW "?\fBCC\fR?\fByymmdd\fR" , +and +.QW "\fBdd-monthname-\fR?\fBCC\fR?\fByy\fR" . +The default year is the current year. If the year is less +than 100, we treat the years 00-68 as 2000-2068 and the years 69-99 +as 1969-1999. Not all platforms can represent the years 38-70, so +an error may result if these years are used. +.TP +\fIISO 8601 point-in-time\fR +An ISO 8601 point-in-time specification, such as +.QW \fICCyymmdd\fBT\fIhhmmss\fR, +where \fBT\fR is the literal +.QW T , +.QW "\fICCyymmdd hhmmss\fR" , +or +.QW \fICCyymmdd\fBT\fIhh:mm:ss\fR . +Note that only these three formats are accepted. +The command does \fInot\fR accept the full range of point-in-time +specifications specified in ISO8601. Other formats can be recognized by +giving an explicit \fB\-format\fR option to the \fBclock scan\fR command. +.TP +\fIrelative time\fR +A specification relative to the current time. The format is \fBnumber +unit\fR. Acceptable units are \fByear\fR, \fBfortnight\fR, +\fBmonth\fR, \fBweek\fR, \fBday\fR, +\fBhour\fR, \fBminute\fR (or \fBmin\fR), and \fBsecond\fR (or \fBsec\fR). The +unit can be specified as a singular or plural, as in \fB3 weeks\fR. +These modifiers may also be specified: +\fBtomorrow\fR, \fByesterday\fR, \fBtoday\fR, \fBnow\fR, +\fBlast\fR, \fBthis\fR, \fBnext\fR, \fBago\fR. +.PP +The actual date is calculated according to the following steps. +.PP +First, any absolute date and/or time is processed and converted. +Using that time as the base, day-of-week specifications are added. +Next, relative specifications are used. If a date or day is +specified, and no absolute or relative time is given, midnight is +used. Finally, a correction is applied so that the correct hour of +the day is produced after allowing for daylight savings time +differences and the correct date is given when going from the end +of a long month to a short month. +.PP +The precedence of the applying of single tokens resp. which sequence will be +used by calculating of the time is complex, e. g. heavily dependent on the +precision of type of the token. +.sp +In example below the second date-string contains "next January", therefore +it results in next year but in January. And third date-string besides "January" +contains also additionally "Fri", so it results in the nearest Friday. +Thus both win before "385 days" resp. make it more precise, because of higher +precision of this token types. +.CS +% clock format [clock scan "5 years 18 months 385 days" -base 0 -gmt 1] -gmt 1 +Thu Jul 21 00:00:00 GMT 1977 +% clock format [clock scan "5 years 18 months 385 days next January" -base 0 -gmt 1] -gmt 1 +Sat Jan 21 00:00:00 GMT 1978 +% clock format [clock scan "5 years 18 months 385 days next January Fri" -base 0 -gmt 1] -gmt 1 +Fri Jan 27 00:00:00 GMT 1978 +.CE +.SH "SEE ALSO" +msgcat(n) +.SH KEYWORDS +clock, date, time +.SH "COPYRIGHT" +Copyright (c) 2004 Kevin B. Kenny . All rights reserved. +'\" Local Variables: +'\" mode: nroff +'\" End: diff --git a/generic/tclClock.c b/generic/tclClock.c index 126c60c..c909096 100644 --- a/generic/tclClock.c +++ b/generic/tclClock.c @@ -146,6 +146,8 @@ static void ClockDeleteCmdProc(ClientData); static int ClockSafeCatchCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); +static void ClockFinalize(void *); + /* * Structure containing description of "native" clock commands to create. */ @@ -213,6 +215,15 @@ TclClockInit( ClockClientData *data; int i; + static int initialized = 0; /* global clock engine initialized (in process) */ + /* + * Register handler to finalize clock on exit. + */ + if (!initialized) { + Tcl_CreateExitHandler(ClockFinalize, NULL); + initialized = 1; + } + /* * Safe interps get [::clock] as alias to a master, so do not need their * own copies of the support routines. @@ -240,6 +251,8 @@ TclClockInit( data->yearOfCenturySwitch = ClockDefaultCenturySwitch; data->validMinYear = INT_MIN; data->validMaxYear = INT_MAX; + /* corresponds max of JDN in sqlite - 9999-12-31 23:59:59 per default */ + data->maxJDN = 5373484.499999994; data->systemTimeZone = NULL; data->systemSetupTZData = NULL; @@ -377,12 +390,14 @@ ClockDeleteCmdProc( for (i = 0; i < MCLIT__END; ++i) { Tcl_DecrRefCount(data->mcLiterals[i]); } + ckfree(data->mcLiterals); data->mcLiterals = NULL; } if (data->mcLitIdxs != NULL) { for (i = 0; i < MCLIT__END; ++i) { Tcl_DecrRefCount(data->mcLitIdxs[i]); } + ckfree(data->mcLitIdxs); data->mcLitIdxs = NULL; } @@ -641,7 +656,7 @@ NormLocaleObj( if ( dataPtr->currentLocale != NULL && ( localeObj == dataPtr->currentLocale || (localeObj->length == dataPtr->currentLocale->length - && strcmp(loc, TclGetString(dataPtr->currentLocale)) == 0 + && strcasecmp(loc, TclGetString(dataPtr->currentLocale)) == 0 ) ) ) { @@ -651,7 +666,7 @@ NormLocaleObj( if ( dataPtr->lastUsedLocale != NULL && ( localeObj == dataPtr->lastUsedLocale || (localeObj->length == dataPtr->lastUsedLocale->length - && strcmp(loc, TclGetString(dataPtr->lastUsedLocale)) == 0 + && strcasecmp(loc, TclGetString(dataPtr->lastUsedLocale)) == 0 ) ) ) { @@ -662,7 +677,7 @@ NormLocaleObj( if ( dataPtr->prevUsedLocale != NULL && ( localeObj == dataPtr->prevUsedLocale || (localeObj->length == dataPtr->prevUsedLocale->length - && strcmp(loc, TclGetString(dataPtr->prevUsedLocale)) == 0 + && strcasecmp(loc, TclGetString(dataPtr->prevUsedLocale)) == 0 ) ) ) { @@ -755,18 +770,21 @@ ClockMCDict(ClockFmtScnCmdArgs *opts) } } + /* check or obtain mcDictObj (be sure it's modifiable) */ if (opts->mcDictObj == NULL || opts->mcDictObj->refCount > 1) { - Tcl_Obj *callargs[2]; + int ref = 1; - /* first try to find it own catalog dict */ + /* first try to find locale catalog dict */ if (dataPtr->mcDicts == NULL) { Tcl_SetObjRef(dataPtr->mcDicts, Tcl_NewDictObj()); } Tcl_DictObjGet(NULL, dataPtr->mcDicts, opts->localeObj, &opts->mcDictObj); - if (opts->mcDictObj == NULL || opts->mcDictObj->refCount > 1) { + if (opts->mcDictObj == NULL) { /* get msgcat dictionary - ::tcl::clock::mcget locale */ + Tcl_Obj *callargs[2]; + callargs[0] = dataPtr->literals[LIT_MCGET]; callargs[1] = opts->localeObj; @@ -776,19 +794,20 @@ ClockMCDict(ClockFmtScnCmdArgs *opts) opts->mcDictObj = Tcl_GetObjResult(opts->interp); Tcl_ResetResult(opts->interp); + ref = 0; /* new object is not yet referenced */ + } - /* be sure that object reference not increases (dict changeable) */ - if (opts->mcDictObj->refCount > 0) { - /* smart reference (shared dict as object with no ref-counter) */ - opts->mcDictObj = Tcl_DictObjSmartRef(opts->interp, - opts->mcDictObj); - } - - /* create exactly one reference to catalog / make it searchable for future */ - Tcl_DictObjPut(NULL, dataPtr->mcDicts, opts->localeObj, + /* be sure that object reference doesn't increase (dict changeable) */ + if (opts->mcDictObj->refCount > ref) { + /* smart reference (shared dict as object with no ref-counter) */ + opts->mcDictObj = Tcl_DictObjSmartRef(opts->interp, opts->mcDictObj); } + /* create exactly one reference to catalog / make it searchable for future */ + Tcl_DictObjPut(NULL, dataPtr->mcDicts, opts->localeObj, + opts->mcDictObj); + if ( opts->localeObj == dataPtr->literals[LIT_C] || opts->localeObj == dataPtr->defaultLocale ) { @@ -938,6 +957,17 @@ TimezoneLoaded( Tcl_Obj *timezoneObj, /* Name of zone was loaded */ Tcl_Obj *tzUnnormObj) /* Name of zone was loaded */ { + /* don't overwrite last-setup with GMT (special case) */ + if (timezoneObj == dataPtr->literals[LIT_GMT]) { + /* mark GMT zone loaded */ + if (dataPtr->gmtSetupTimeZone == NULL) { + Tcl_SetObjRef(dataPtr->gmtSetupTimeZone, + dataPtr->literals[LIT_GMT]); + } + Tcl_SetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, tzUnnormObj); + return; + } + /* last setup zone loaded */ if (dataPtr->lastSetupTimeZone != timezoneObj) { SavePrevTimezoneObj(dataPtr); @@ -945,14 +975,6 @@ TimezoneLoaded( Tcl_UnsetObjRef(dataPtr->lastSetupTZData); } Tcl_SetObjRef(dataPtr->lastSetupTimeZoneUnnorm, tzUnnormObj); - - /* mark GMT zone loaded */ - if ( dataPtr->gmtSetupTimeZone == NULL - && timezoneObj == dataPtr->literals[LIT_GMT] - ) { - Tcl_SetObjRef(dataPtr->gmtSetupTimeZone, - dataPtr->literals[LIT_GMT]); - } } /* *---------------------------------------------------------------------- @@ -986,14 +1008,14 @@ ClockConfigureObjCmd( "-system-tz", "-setup-tz", "-default-locale", "-current-locale", "-clear", "-year-century", "-century-switch", - "-min-year", "-max-year", "-validate", + "-min-year", "-max-year", "-max-jdn", "-validate", NULL }; enum optionInd { CLOCK_SYSTEM_TZ, CLOCK_SETUP_TZ, CLOCK_DEFAULT_LOCALE, CLOCK_CURRENT_LOCALE, CLOCK_CLEAR_CACHE, CLOCK_YEAR_CENTURY, CLOCK_CENTURY_SWITCH, - CLOCK_MIN_YEAR, CLOCK_MAX_YEAR, CLOCK_VALIDATE + CLOCK_MIN_YEAR, CLOCK_MAX_YEAR, CLOCK_MAX_JDN, CLOCK_VALIDATE }; int optionIndex; /* Index of an option. */ int i; @@ -1122,6 +1144,21 @@ ClockConfigureObjCmd( Tcl_NewIntObj(dataPtr->validMaxYear)); } break; + case CLOCK_MAX_JDN: + if (i < objc) { + double jd; + if (Tcl_GetDoubleFromObj(interp, objv[i], &jd) != TCL_OK) { + return TCL_ERROR; + } + dataPtr->maxJDN = jd; + Tcl_SetObjResult(interp, objv[i]); + continue; + } + if (i+1 >= objc) { + Tcl_SetObjResult(interp, + Tcl_NewDoubleObj(dataPtr->maxJDN)); + } + break; case CLOCK_VALIDATE: if (i < objc) { int val; @@ -1355,20 +1392,20 @@ ClockSetupTimeZone( Tcl_Obj * ClockFormatNumericTimeZone(int z) { - char sign = '+'; - int h, m; + char buf[12+1], *p; + if ( z < 0 ) { z = -z; - sign = '-'; + *buf = '-'; + } else { + *buf = '+'; } - h = z / 3600; - z %= 3600; - m = z / 60; - z %= 60; + TclItoAw(buf+1, z / 3600, '0', 2); z %= 3600; + p = TclItoAw(buf+3, z / 60, '0', 2); z %= 60; if (z != 0) { - return Tcl_ObjPrintf("%c%02d%02d%02d", sign, h, m, z); + p = TclItoAw(buf+5, z, '0', 2); } - return Tcl_ObjPrintf("%c%02d%02d", sign, h, m); + return Tcl_NewStringObj(buf, p - buf); } /* @@ -1605,11 +1642,11 @@ ClockGetDateFields( } /* - * Extract Julian day. + * Extract Julian day and seconds of the day. */ - fields->julianDay = (fields->localSeconds + JULIAN_SEC_POSIX_EPOCH) - / SECONDS_PER_DAY; + ClockExtractJDAndSODFromSeconds(fields->julianDay, fields->secondOfDay, + fields->localSeconds); /* * Convert to Julian or Gregorian calendar. @@ -1619,15 +1656,6 @@ ClockGetDateFields( GetMonthDay(fields); GetYearWeekDay(fields, changeover); - - /* - * Seconds of the day. - */ - fields->secondOfDay = (int)(fields->localSeconds % SECONDS_PER_DAY); - if (fields->secondOfDay < 0) { - fields->secondOfDay += SECONDS_PER_DAY; - } - return TCL_OK; } @@ -2091,19 +2119,14 @@ ConvertLocalToUTCUsingC( struct tm timeVal; int localErrno; int secondOfDay; - Tcl_WideInt jsec; /* * Convert the given time to a date. */ - jsec = fields->localSeconds + JULIAN_SEC_POSIX_EPOCH; - fields->julianDay = (jsec / SECONDS_PER_DAY); - secondOfDay = (int)(jsec % SECONDS_PER_DAY); - if (secondOfDay < 0) { - secondOfDay += SECONDS_PER_DAY; - fields->julianDay--; - } + ClockExtractJDAndSODFromSeconds(fields->julianDay, secondOfDay, + fields->localSeconds); + GetGregorianEraYearDay(fields, changeover); GetMonthDay(fields); @@ -2238,6 +2261,9 @@ ConvertUTCToLocal( return TCL_ERROR; } + /* signal we need to revalidate TZ epoch next time fields gets used. */ + fields->flags |= CLF_CTZ; + /* we cannot cache (ranges unknown yet) */ } else { Tcl_WideInt rangesVal[2]; @@ -2247,6 +2273,9 @@ ConvertUTCToLocal( return TCL_ERROR; } + /* converted using table (TZ isn't :localtime) */ + fields->flags &= ~CLF_CTZ; + /* Cache the last conversion */ if (ltzoc != NULL) { /* slot was found above */ /* timezoneObj and changeover are the same */ @@ -2349,7 +2378,7 @@ ConvertUTCToLocalUsingC( time_t tock; struct tm *timeVal; /* Time after conversion */ int diff; /* Time zone diff local-Greenwich */ - char buffer[8]; /* Buffer for time zone name */ + char buffer[8], *p; /* Buffer for time zone name */ /* * Use 'localtime' to determine local year, month, day, time of day. @@ -2402,14 +2431,12 @@ ConvertUTCToLocalUsingC( } else { *buffer = '+'; } - sprintf(buffer+1, "%02d", diff / 3600); - diff %= 3600; - sprintf(buffer+3, "%02d", diff / 60); - diff %= 60; - if (diff > 0) { - sprintf(buffer+5, "%02d", diff); + TclItoAw(buffer+1, diff / 3600, '0', 2); diff %= 3600; + p = TclItoAw(buffer+3, diff / 60, '0', 2); diff %= 60; + if (diff != 0) { + p = TclItoAw(buffer+5, diff, '0', 2); } - Tcl_SetObjRef(fields->tzName, Tcl_NewStringObj(buffer, -1)); + Tcl_SetObjRef(fields->tzName, Tcl_NewStringObj(buffer, p - buffer)); return TCL_OK; } @@ -3416,28 +3443,26 @@ ClockParseFmtScnArgs( /* Base (by scan or add) or clock value (by format) */ if (opts->baseObj != NULL) { - register Tcl_Obj *baseObj = opts->baseObj; - /* bypass integer recognition if looks like option "-now" */ - if ( - (baseObj->length == 4 && baseObj->bytes && *(baseObj->bytes+1) == 'n') || - TclGetWideIntFromObj(NULL, baseObj, &baseVal) != TCL_OK - ) { - - /* we accept "-now" as current date-time */ + Tcl_Obj *baseObj = opts->baseObj; + + /* bypass integer recognition if looks like "now" or "-now" */ + if ((baseObj->bytes && + ((baseObj->length == 3 && baseObj->bytes[0] == 'n') || + (baseObj->length == 4 && baseObj->bytes[1] == 'n'))) + || TclGetWideIntFromObj(NULL, baseObj, &baseVal) != TCL_OK) { + /* we accept "now" and "-now" as current date-time */ static const char *const nowOpts[] = { - "-now", NULL + "now", "-now", NULL }; int idx; - if (Tcl_GetIndexFromObj(NULL, baseObj, nowOpts, "seconds or -now", - TCL_EXACT, &idx) == TCL_OK - ) { + if (Tcl_GetIndexFromObj(NULL, baseObj, nowOpts, "seconds", + TCL_EXACT, &idx) == TCL_OK) { goto baseNow; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "expected integer but got \"%s\"", - Tcl_GetString(baseObj))); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", NULL); + "bad seconds \"%s\": must be now or integer", + TclGetString(baseObj))); i = 1; goto badOption; } @@ -3473,7 +3498,9 @@ ClockParseFmtScnArgs( /* check base fields already cached (by TZ, last-second cache) */ if ( dataPtr->lastBase.timezoneObj == opts->timezoneObj - && dataPtr->lastBase.date.seconds == baseVal) { + && dataPtr->lastBase.date.seconds == baseVal + && (!(dataPtr->lastBase.date.flags & CLF_CTZ) + || dataPtr->lastTZEpoch == TzsetIfNecessary())) { canUseLastBase = 1; /* * There's one exception: If TZ changed, and if timezone is just the @@ -3546,9 +3573,8 @@ ClockFormatObjCmd( int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter values */ { - ClockClientData *dataPtr = clientData; - - static const char *syntax = "clock format clockval|-now " + ClockClientData *dataPtr = (ClockClientData *)clientData; + static const char *syntax = "clock format clockval|now " "?-format string? " "?-gmt boolean? " "?-locale LOCALE? ?-timezone ZONE?"; @@ -3696,6 +3722,20 @@ ClockScanObjCmd( goto done; } + /* + * If no GMT and not free-scan (where valid stage 1 is done in-between), + * validate with stage 1 before local time conversion, otherwise it may + * adjust date/time tokens to valid values + */ + if ( (opts.flags & CLF_VALIDATE_S1) && + info->flags & (CLF_ASSEMBLE_SECONDS|CLF_LOCALSEC) + ) { + ret = ClockValidDate(&yy, &opts, CLF_VALIDATE_S1); + if (ret != TCL_OK) { + goto done; + } + } + /* Convert date info structure into UTC seconds */ ret = ClockScanCommit(&yy, &opts); @@ -3703,9 +3743,9 @@ ClockScanObjCmd( goto done; } - /* Apply validation rules, if expected */ + /* Apply remaining validation rules, if expected */ if ( (opts.flags & CLF_VALIDATE) ) { - ret = ClockValidDate(&yy, &opts, opts.formatObj == NULL ? 2 : 3); + ret = ClockValidDate(&yy, &opts, opts.flags & CLF_VALIDATE); if (ret != TCL_OK) { goto done; } @@ -3766,9 +3806,12 @@ ClockScanCommit( info->flags &= ~CLF_ASSEMBLE_JULIANDAY; } - /* some overflow checks, if not extended */ - if (!(opts->flags & CLF_EXTENDED)) { - if (yydate.julianDay > 5373484) { + /* some overflow checks */ + if (info->flags & CLF_JULIANDAY) { + ClockClientData *dataPtr = opts->clientData; + double curJDN = (double)yydate.julianDay + + ((double)yySecondOfDay - SECONDS_PER_DAY/2) / SECONDS_PER_DAY; + if (curJDN > dataPtr->maxJDN) { Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "requested date too large to represent", -1)); Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", NULL); @@ -3776,13 +3819,19 @@ ClockScanCommit( } } + /* If seconds overflows the day (no validate case), increase days */ + if (yySecondOfDay >= SECONDS_PER_DAY) { + yydate.julianDay += (yySecondOfDay / SECONDS_PER_DAY); + yySecondOfDay %= SECONDS_PER_DAY; + } + /* Local seconds to UTC (stored in yydate.seconds) */ if (info->flags & (CLF_ASSEMBLE_SECONDS)) { yydate.localSeconds = - -210866803200L - + ( SECONDS_PER_DAY * (Tcl_WideInt)yydate.julianDay ) - + ( yySecondOfDay % SECONDS_PER_DAY ); + -210866803200LL + + (SECONDS_PER_DAY * yydate.julianDay) + + yySecondOfDay; } if (info->flags & (CLF_ASSEMBLE_SECONDS|CLF_LOCALSEC)) { @@ -3834,19 +3883,20 @@ ClockValidDate( yydate.tzOffset); #endif - if (!(stage & 1)) { + if (!(stage & CLF_VALIDATE_S1) || !(opts->flags & CLF_VALIDATE_S1)) { goto stage_2; } + opts->flags &= ~CLF_VALIDATE_S1; /* stage 1 is done */ /* first year (used later in hath / daysInPriorMonths) */ - if ((info->flags & (CLF_YEAR|CLF_ISO8601YEAR)) || yyHaveDate) { + if ((info->flags & (CLF_YEAR|CLF_ISO8601YEAR))) { if ((info->flags & CLF_ISO8601YEAR)) { if ( yydate.iso8601Year < dataPtr->validMinYear || yydate.iso8601Year > dataPtr->validMaxYear ) { errMsg = "invalid iso year"; errCode = "iso year"; goto error; } } - if ((info->flags & CLF_YEAR) || yyHaveDate) { + if (info->flags & CLF_YEAR) { if ( yyYear < dataPtr->validMinYear || yyYear > dataPtr->validMaxYear ) { errMsg = "invalid year"; errCode = "year"; goto error; @@ -3862,15 +3912,13 @@ ClockValidDate( } } /* and month (used later in hath) */ - if ((info->flags & CLF_MONTH) || yyHaveDate) { - info->flags |= CLF_MONTH; + if (info->flags & CLF_MONTH) { if ( yyMonth < 1 || yyMonth > 12 ) { errMsg = "invalid month"; errCode = "month"; goto error; } } /* day of month */ - if ((info->flags & CLF_DAYOFMONTH) || (yyHaveDate || yyHaveDay)) { - info->flags |= CLF_DAYOFMONTH; + if (info->flags & (CLF_DAYOFMONTH|CLF_DAYOFWEEK)) { if ( yyDay < 1 || yyDay > 31 ) { errMsg = "invalid day"; errCode = "day"; goto error; } @@ -3882,7 +3930,7 @@ ClockValidDate( } } } - if ((info->flags & CLF_DAYOFYEAR)) { + if (info->flags & CLF_DAYOFYEAR) { if ( yydate.dayOfYear < 1 || yydate.dayOfYear > daysInPriorMonths[IsGregorianLeapYear(&yydate)][12] ) { errMsg = "invalid day of year"; errCode = "day of year"; goto error; @@ -3902,7 +3950,7 @@ ClockValidDate( } } - if ((info->flags & CLF_TIME) || yyHaveTime) { + if (info->flags & CLF_TIME) { /* hour */ if ( yyHour < 0 || yyHour > ((yyMeridian == MER24) ? 23 : 12) ) { errMsg = "invalid time (hour)"; errCode = "hour"; goto error; @@ -3917,9 +3965,10 @@ ClockValidDate( } } - if (!(stage & 2)) { + if (!(stage & CLF_VALIDATE_S2) || !(opts->flags & CLF_VALIDATE_S2)) { return TCL_OK; } + opts->flags &= ~CLF_VALIDATE_S2; /* stage 2 is done */ /* * Further tests expected ready calculated julianDay (inclusive relative), @@ -3929,7 +3978,7 @@ ClockValidDate( /* time, regarding the modifications by the time-zone (looks for given time * in between DST-time hole, so does not exist in this time-zone) */ - if (((info->flags & CLF_TIME) || yyHaveTime)) { + if (info->flags & CLF_TIME) { /* * we don't need to do the backwards time-conversion (UTC to local) and * compare results, because the after conversion (local to UTC) we @@ -4016,7 +4065,7 @@ ClockFreeScan( * midnight. */ - if (yyHaveDate) { + if (info->flags & CLF_YEAR) { if (yyYear < 100) { if (yyYear >= dataPtr->yearOfCenturySwitch) { yyYear -= 100; @@ -4024,9 +4073,6 @@ ClockFreeScan( yyYear += dataPtr->currentYearCentury; } yydate.era = CE; - if (yyHaveTime == 0) { - yyHaveTime = -1; - } info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; } @@ -4035,17 +4081,24 @@ ClockFreeScan( * zone indicator of +-hhmm and setup this time zone. */ - if (yyHaveZone) { - Tcl_Obj *tzObjStor = NULL; - int minEast = -yyTimezone; - int dstFlag = 1 - yyDSTmode; - tzObjStor = ClockFormatNumericTimeZone( - 60 * minEast + 3600 * dstFlag); - Tcl_IncrRefCount(tzObjStor); + if (info->flags & CLF_ZONE) { + if (yyTimezone || !yyDSTmode) { + /* Real time zone from numeric zone */ + Tcl_Obj *tzObjStor = NULL; + int minEast = -yyTimezone; + int dstFlag = 1 - yyDSTmode; + tzObjStor = ClockFormatNumericTimeZone( + 60 * minEast + 3600 * dstFlag); + Tcl_IncrRefCount(tzObjStor); - opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor); + opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor); - Tcl_DecrRefCount(tzObjStor); + Tcl_DecrRefCount(tzObjStor); + } else { + /* simplest case - GMT / UTC */ + opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, + dataPtr->literals[LIT_GMT]); + } if (opts->timezoneObj == NULL) { goto done; } @@ -4060,7 +4113,7 @@ ClockFreeScan( * relative time (otherwise always valid recalculated date & time). */ if ( (opts->flags & CLF_VALIDATE) ) { - if (ClockValidDate(info, opts, 1) != TCL_OK) { + if (ClockValidDate(info, opts, CLF_VALIDATE_S1) != TCL_OK) { goto done; } } @@ -4069,20 +4122,20 @@ ClockFreeScan( * Assemble date, time, zone into seconds-from-epoch */ - if (yyHaveTime == -1) { + if ((info->flags & (CLF_TIME|CLF_HAVEDATE)) == CLF_HAVEDATE) { yySecondOfDay = 0; info->flags |= CLF_ASSEMBLE_SECONDS; } else - if (yyHaveTime) { + if (info->flags & CLF_TIME) { yySecondOfDay = ToSeconds(yyHour, yyMinutes, yySeconds, yyMeridian); info->flags |= CLF_ASSEMBLE_SECONDS; } else - if ( (yyHaveDay && !yyHaveDate) - || yyHaveOrdinalMonth - || ( yyHaveRel + if ( (info->flags & (CLF_DAYOFWEEK|CLF_HAVEDATE)) == CLF_DAYOFWEEK + || (info->flags & CLF_ORDINALMONTH) + || ( (info->flags & CLF_RELCONV) && ( yyRelMonth != 0 || yyRelDay != 0 ) ) ) { @@ -4135,7 +4188,7 @@ ClockCalcRelTime( */ repeat_rel: - if (yyHaveRel) { + if (info->flags & CLF_RELCONV) { /* * Relative conversion normally possible in UTC time only, because @@ -4199,7 +4252,7 @@ ClockCalcRelTime( /* relative time (seconds), if exceeds current date, do the day conversion and * leave rest of the increment in yyRelSeconds to add it hereafter in UTC seconds */ if (yyRelSeconds) { - int newSecs = yySecondOfDay + yyRelSeconds; + Tcl_WideInt newSecs = yySecondOfDay + yyRelSeconds; /* if seconds increment outside of current date, increment day */ if (newSecs / SECONDS_PER_DAY != yySecondOfDay / SECONDS_PER_DAY) { @@ -4212,14 +4265,14 @@ ClockCalcRelTime( } } - yyHaveRel = 0; + info->flags &= ~CLF_RELCONV; } /* * Do relative (ordinal) month */ - if (yyHaveOrdinalMonth) { + if (info->flags & CLF_ORDINALMONTH) { int monthDiff; /* if needed extract year, month, etc. again */ @@ -4245,12 +4298,10 @@ ClockCalcRelTime( } /* process it further via relative times */ - yyHaveRel++; yyYear += yyMonthOrdinalIncr; yyRelMonth += monthDiff; - yyHaveOrdinalMonth = 0; - - info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; + info->flags &= ~CLF_ORDINALMONTH; + info->flags |= CLF_RELCONV|CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; goto repeat_rel; } @@ -4259,12 +4310,11 @@ ClockCalcRelTime( * Do relative weekday */ - if (yyHaveDay && !yyHaveDate) { + if ((info->flags & (CLF_DAYOFWEEK|CLF_HAVEDATE)) == CLF_DAYOFWEEK) { /* restore scanned day of week */ - if (info->flags & CLF_DAYOFWEEK) { - yyDayOfWeek = prevDayOfWeek; - } + yyDayOfWeek = prevDayOfWeek; + /* if needed assemble julianDay now */ if (info->flags & CLF_ASSEMBLE_JULIANDAY) { GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE); @@ -4390,7 +4440,7 @@ ClockAddObjCmd( int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter values */ { - static const char *syntax = "clock add clockval|-now ?number units?..." + static const char *syntax = "clock add clockval|now ?number units?..." "?-gmt boolean? " "?-locale LOCALE? ?-timezone ZONE?"; ClockClientData *dataPtr = clientData; @@ -4447,19 +4497,22 @@ ClockAddObjCmd( */ for (i = 2; i < objc; i+=2) { - /* bypass not integers (options, allready processed above) */ + /* bypass not integers (options, allready processed above in ClockParseFmtScnArgs) */ if (TclGetWideIntFromObj(NULL, objv[i], &offs) != TCL_OK) { continue; } - if (objv[i]->typePtr == tclBignumTypePtr) { - Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]); - goto done; - } /* get unit */ if (Tcl_GetIndexFromObj(interp, objv[i+1], units, "unit", 0, &unitIndex) != TCL_OK) { goto done; } + if (objv[i]->typePtr == tclBignumTypePtr + || offs > (unitIndex < CLC_ADD_HOURS ? 0x7fffffff : TCL_MAX_SECONDS) + || offs < (unitIndex < CLC_ADD_HOURS ? -0x7fffffff : TCL_MIN_SECONDS) + ) { + Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]); + goto done; + } /* nothing to do if zero quantity */ if (!offs) { @@ -4470,7 +4523,7 @@ ClockAddObjCmd( * correct date info, because the date may be changed, * so refresh it now */ - if ( yyHaveRel + if ( (info->flags & CLF_RELCONV) && ( unitIndex == CLC_ADD_WEEKDAYS /* some months can be shorter as another */ || yyRelMonth || yyRelDay @@ -4485,7 +4538,7 @@ ClockAddObjCmd( } /* process increment by offset + unit */ - yyHaveRel++; + info->flags |= CLF_RELCONV; switch (unitIndex) { case CLC_ADD_YEARS: yyRelMonth += offs * 12; @@ -4522,7 +4575,7 @@ ClockAddObjCmd( * Do relative times (if not yet already processed interim): */ - if (yyHaveRel) { + if (info->flags & CLF_RELCONV) { if (ClockCalcRelTime(info, &opts) != TCL_OK) { goto done; } @@ -4666,12 +4719,11 @@ ClockSafeCatchCmd( * *---------------------------------------------------------------------- */ - +static char* tzWas = INT2PTR(-1); /* Previous value of TZ, protected by + * clockMutex. */ static size_t TzsetIfNecessary(void) { - static char* tzWas = INT2PTR(-1); /* Previous value of TZ, protected by - * clockMutex. */ static long tzLastRefresh = 0; /* Used for latency before next refresh */ static size_t tzWasEpoch = 0; /* Epoch, signals that TZ changed */ static size_t tzEnvEpoch = 0; /* Last env epoch, for faster signaling, @@ -4718,6 +4770,19 @@ TzsetIfNecessary(void) return tzWasEpoch; } +static void +ClockFinalize( + ClientData clientData) +{ + ClockFrmScnFinalize(); + + if (tzWas && tzWas != INT2PTR(-1)) { + ckfree(tzWas); + } + + Tcl_MutexFinalize(&clockMutex); +} + /* * Local Variables: * mode: c diff --git a/generic/tclClockFmt.c b/generic/tclClockFmt.c index 254936c..851a78d 100644 --- a/generic/tclClockFmt.c +++ b/generic/tclClockFmt.c @@ -32,7 +32,9 @@ TCL_DECLARE_MUTEX(ClockFmtMutex); /* Serializes access to common format list. */ static void ClockFmtScnStorageDelete(ClockFmtScnStorage *fss); -static void ClockFrmScnFinalize(ClientData clientData); +#ifndef TCL_CLOCK_FULL_COMPAT +#define TCL_CLOCK_FULL_COMPAT 1 +#endif /* * Clock scan and format facilities. @@ -56,6 +58,47 @@ static void ClockFrmScnFinalize(ClientData clientData); *---------------------------------------------------------------------- */ +static inline void +_str2int_no( + int *out, + register + const char *p, + const char *e, + int sign) +{ + /* assert(e <= p+10); */ + register int val = 0; + /* overflow impossible for 10 digits ("9..9"), so no needs to check at all */ + while (p < e) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign < 0) { val = -val; } + *out = val; +} + +static inline void +_str2wideInt_no( + Tcl_WideInt *out, + register + const char *p, + const char *e, + int sign) +{ + /* assert(e <= p+18); */ + register Tcl_WideInt val = 0; + /* overflow impossible for 18 digits ("9..9"), so no needs to check at all */ + while (p < e) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } + if (sign < 0) { val = -val; } + *out = val; +} + +/* int & Tcl_WideInt overflows may happens here (expected case) */ +#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) +# pragma GCC optimize("no-trapv") +#endif + static inline int _str2int( int *out, @@ -64,22 +107,31 @@ _str2int( const char *e, int sign) { - register int val = 0, prev = 0; + register int val = 0; + /* overflow impossible for 10 digits ("9..9"), so no needs to check before */ + const char *eNO = p+10; + if (eNO > e) { + eNO = e; + } + while (p < eNO) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } if (sign >= 0) { - while (p < e) { + while (p < e) { /* check for overflow */ + int prev = val; val = val * 10 + (*p++ - '0'); - if (val < prev) { + if (val / 10 < prev) { return TCL_ERROR; } - prev = val; } } else { - while (p < e) { + val = -val; + while (p < e) { /* check for overflow */ + int prev = val; val = val * 10 - (*p++ - '0'); - if (val > prev) { + if (val / 10 > prev) { return TCL_ERROR; } - prev = val; } } *out = val; @@ -94,27 +146,50 @@ _str2wideInt( const char *e, int sign) { - register Tcl_WideInt val = 0, prev = 0; + register Tcl_WideInt val = 0; + /* overflow impossible for 18 digits ("9..9"), so no needs to check before */ + const char *eNO = p+18; + if (eNO > e) { + eNO = e; + } + while (p < eNO) { /* never overflows */ + val = val * 10 + (*p++ - '0'); + } if (sign >= 0) { - while (p < e) { + while (p < e) { /* check for overflow */ + Tcl_WideInt prev = val; val = val * 10 + (*p++ - '0'); - if (val < prev) { + if (val / 10 < prev) { return TCL_ERROR; } - prev = val; } } else { - while (p < e) { + val = -val; + while (p < e) { /* check for overflow */ + Tcl_WideInt prev = val; val = val * 10 - (*p++ - '0'); - if (val > prev) { + if (val / 10 > prev) { return TCL_ERROR; } - prev = val; } } *out = val; return TCL_OK; } + +int +TclAtoWIe( + Tcl_WideInt *out, + const char *p, + const char *e, + int sign) +{ + return _str2wideInt(out, p, e, sign); +} + +#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) +# pragma GCC reset_options +#endif /* *---------------------------------------------------------------------- @@ -199,6 +274,15 @@ _itoaw( return buf + width; } +char * +TclItoAw( + char *buf, + int val, + char padchar, + unsigned short int width) +{ + return _itoaw(buf, val, padchar, width); +} static inline char * _witoaw( @@ -247,7 +331,7 @@ _witoaw( if (!width) width++; /* check resp. recalculate width (regarding sign) */ width--; - if (val <= 10000000000L) { + if (val <= -10000000000L) { Tcl_WideInt val2; val2 = val / 10000000000L; while (width <= 9 && val2 <= -wrange[width]) { @@ -560,7 +644,7 @@ ClockFmtObj_FreeInternalRep(objPtr) Tcl_Obj *objPtr; { ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr); - if (fss != NULL) { + if (fss != NULL && initialized) { Tcl_MutexLock(&ClockFmtMutex); /* decrement object reference count of format/scan storage */ if (--fss->objRefCount <= 0) { @@ -714,7 +798,6 @@ FindOrCreateFmtScnStorage( &ClockFmtScnStorageHashKeyType); initialized = 1; - Tcl_CreateExitHandler(ClockFrmScnFinalize, NULL); } /* get or create entry (and alocate storage) */ @@ -844,14 +927,21 @@ ClockLocalizeFormat( callargs[0] = dataPtr->literals[LIT_LOCALIZE_FORMAT]; callargs[1] = opts->localeObj; callargs[2] = opts->formatObj; - callargs[3] = keyObj; - if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) != TCL_OK + callargs[3] = opts->mcDictObj; + if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) == TCL_OK ) { - goto done; + valObj = Tcl_GetObjResult(opts->interp); } - valObj = Tcl_GetObjResult(opts->interp); - + /* ensure mcDictObj remains unshared */ + if (opts->mcDictObj->refCount > 1) { + /* smart reference (shared dict as object with no ref-counter) */ + opts->mcDictObj = Tcl_DictObjSmartRef(opts->interp, + opts->mcDictObj); + } + if (!valObj) { + goto done; + } /* cache it inside mc-dictionary (this incr. ref count of keyObj/valObj) */ if (Tcl_DictObjPut(opts->interp, opts->mcDictObj, keyObj, valObj) != TCL_OK @@ -898,7 +988,8 @@ static const char * FindTokenBegin( register const char *p, register const char *end, - ClockScanToken *tok) + ClockScanToken *tok, + int flags) { char c; if (p < end) { @@ -906,25 +997,33 @@ FindTokenBegin( switch (tok->map->type) { case CTOKT_INT: case CTOKT_WIDE: - /* should match at least one digit */ - while (!isdigit(UCHAR(*p)) && (p = TclUtfNext(p)) < end) {}; + if (!(flags & CLF_STRICT)) { + /* should match at least one digit or space */ + while (!isdigit(UCHAR(*p)) && !isspace(UCHAR(*p)) && + (p = Tcl_UtfNext(p)) < end) {} + } else { + /* should match at least one digit */ + while (!isdigit(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {} + } return p; - break; case CTOKT_WORD: c = *(tok->tokWord.start); - /* should match at least to the first char of this word */ - while (*p != c && (p = TclUtfNext(p)) < end) {}; - return p; - break; + goto findChar; case CTOKT_SPACE: - while (!isspace(UCHAR(*p)) && (p = TclUtfNext(p)) < end) {}; + while (!isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {}; return p; - break; case CTOKT_CHAR: c = *((char *)tok->map->data); - while (*p != c && (p = TclUtfNext(p)) < end) {}; +findChar: + if (!(flags & CLF_STRICT)) { + /* should match the char or space */ + while (*p != c && !isspace(UCHAR(*p)) && + (p = Tcl_UtfNext(p)) < end) {} + } else { + /* should match the char */ + while (*p != c && (p = Tcl_UtfNext(p)) < end) {} + } return p; - break; } } return p; @@ -947,9 +1046,12 @@ FindTokenBegin( */ static void -DetermineGreedySearchLen(ClockFmtScnCmdArgs *opts, - DateInfo *info, ClockScanToken *tok, - int *minLenPtr, int *maxLenPtr) +DetermineGreedySearchLen( + ClockFmtScnCmdArgs *opts, + DateInfo *info, + ClockScanToken *tok, + int *minLenPtr, + int *maxLenPtr) { register int minLen = tok->map->minSize; register int maxLen; @@ -960,7 +1062,8 @@ DetermineGreedySearchLen(ClockFmtScnCmdArgs *opts, if ((tok+1)->map) { end -= tok->endDistance + yySpaceCount; /* find position of next known token */ - p = FindTokenBegin(p, end, tok+1); + p = FindTokenBegin(p, end, tok+1, + TCL_CLOCK_FULL_COMPAT ? opts->flags : CLF_STRICT); if (p < end) { minLen = p - yyInput; } @@ -1003,10 +1106,10 @@ DetermineGreedySearchLen(ClockFmtScnCmdArgs *opts, } p += tok->lookAhMin; if (laTok->map && p < end) { - const char *f; /* try to find laTok between [lookAhMin, lookAhMax] */ while (minLen < maxLen) { - f = FindTokenBegin(p, end, laTok); + const char *f = FindTokenBegin(p, end, laTok, + TCL_CLOCK_FULL_COMPAT ? opts->flags : CLF_STRICT); /* if found (not below lookAhMax) */ if (f < end) { break; @@ -1584,6 +1687,72 @@ ClockScnToken_LocaleListMatcher_Proc(ClockFmtScnCmdArgs *opts, } static int +ClockScnToken_JDN_Proc(ClockFmtScnCmdArgs *opts, + DateInfo *info, ClockScanToken *tok) +{ + int minLen, maxLen; + register const char *p = yyInput, *end; const char *s; + Tcl_WideInt intJD; int fractJD = 0, fractJDDiv = 1; + + DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); + + end = yyInput + maxLen; + + /* currently positive astronomic dates only */ + if (*p == '+' || *p == '-') { p++; }; + s = p; + while (p < end && isdigit(UCHAR(*p))) { + p++; + } + if ( _str2wideInt(&intJD, s, p, (*yyInput != '-' ? 1 : -1)) != TCL_OK) { + return TCL_RETURN; + }; + yyInput = p; + if (p >= end || *p++ != '.') { /* allow pure integer JDN */ + /* by astronomical JD the seconds of day offs is 12 hours */ + if (tok->map->offs) { + goto done; + } + /* calendar JD */ + yydate.julianDay = intJD; + return TCL_OK; + } + s = p; + while (p < end && isdigit(UCHAR(*p))) { + fractJDDiv *= 10; + p++; + } + if ( _str2int(&fractJD, s, p, 1) != TCL_OK) { + return TCL_RETURN; + }; + yyInput = p; + +done: + /* + * Build a date from julian day (integer and fraction). + * Note, astronomical JDN starts at noon in opposite to calendar julianday. + */ + + fractJD = (int)tok->map->offs /* 0 for calendar or 43200 for astro JD */ + + (int)((Tcl_WideInt)SECONDS_PER_DAY * fractJD / fractJDDiv); + if (fractJD >= SECONDS_PER_DAY) { + fractJD %= SECONDS_PER_DAY; + intJD += 1; + } + yydate.secondOfDay = fractJD; + yydate.julianDay = intJD; + + yydate.seconds = + -210866803200L + + ( SECONDS_PER_DAY * intJD ) + + ( fractJD ); + + info->flags |= CLF_POSIXSEC; + + return TCL_OK; +} + +static int ClockScnToken_TimeZone_Proc(ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { @@ -1752,7 +1921,7 @@ static ClockScanTokenMap ScnSTokenMap[] = { NULL}, /* %b %B %h */ {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, 0, - ClockScnToken_Month_Proc}, + ClockScnToken_Month_Proc, NULL}, /* %y */ {CTOKT_INT, CLF_YEAR, 0, 1, 2, TclOffset(DateInfo, date.year), NULL}, @@ -1772,7 +1941,7 @@ static ClockScanTokenMap ScnSTokenMap[] = { {CTOKT_PARSER, 0, 0, 0, 0xffff, 0, ClockScnToken_amPmInd_Proc, NULL}, /* %J */ - {CTOKT_WIDE, CLF_JULIANDAY, 0, 1, 0xffff, TclOffset(DateInfo, date.julianDay), + {CTOKT_WIDE, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, TclOffset(DateInfo, date.julianDay), NULL}, /* %j */ {CTOKT_INT, CLF_DAYOFYEAR, 0, 1, 3, TclOffset(DateInfo, date.dayOfYear), @@ -1815,11 +1984,17 @@ static const char *ScnSTokenMapAliasIndex[2] = { }; static const char *ScnETokenMapIndex = - "Eys"; + "EJjys"; static ClockScanTokenMap ScnETokenMap[] = { /* %EE */ {CTOKT_PARSER, 0, 0, 0, 0xffff, TclOffset(DateInfo, date.year), ClockScnToken_LocaleERA_Proc, (void *)MCLIT_LOCALE_NUMERALS}, + /* %EJ */ + {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, 0, /* calendar JDN starts at midnight */ + ClockScnToken_JDN_Proc, NULL}, + /* %Ej */ + {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */ + ClockScnToken_JDN_Proc, NULL}, /* %Ey */ {CTOKT_PARSER, 0, 0, 0, 0xffff, 0, /* currently no capture, parse only token */ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, @@ -1900,14 +2075,13 @@ EstimateTokenCount( return ++tokcnt; } -#define AllocTokenInChain(tok, chain, tokCnt) \ - if (++(tok) >= (chain) + (tokCnt)) { \ - chain = ckrealloc((char *)(chain), \ +#define AllocTokenInChain(tok, chain, tokCnt) \ + if (++(tok) >= (chain) + (tokCnt)) { \ + chain = ckrealloc((char *)(chain), \ (tokCnt + CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE) * sizeof(*(tok))); \ - if ((chain) == NULL) { goto done; }; \ - (tok) = (chain) + (tokCnt); \ - (tokCnt) += CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE; \ - } \ + (tok) = (chain) + (tokCnt); \ + (tokCnt) += CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE; \ + } \ memset(tok, 0, sizeof(*(tok))); /* @@ -2035,37 +2209,47 @@ ClockGetOrParseScanFormat( } break; default: - if ( *p == ' ' || isspace(UCHAR(*p)) ) { - tok->map = &ScnSpaceTokenMap; - tok->tokWord.start = p++; - while (p < e && isspace(UCHAR(*p))) { - p++; - } - tok->tokWord.end = p; - /* increase space count used in format */ - fss->scnSpaceCount++; - /* next token */ - AllocTokenInChain(tok, scnTok, fss->scnTokC); tokCnt++; - continue; - } -word_tok: - if (1) { - ClockScanToken *wordTok = tok; - if (tok > scnTok && (tok-1)->map == &ScnWordTokenMap) { - wordTok = tok-1; + if (isspace(UCHAR(*p))) { + tok->map = &ScnSpaceTokenMap; + tok->tokWord.start = p++; + while (p < e && isspace(UCHAR(*p))) { + p++; + } + tok->tokWord.end = p; + /* increase space count used in format */ + fss->scnSpaceCount++; + /* next token */ + AllocTokenInChain(tok, scnTok, fss->scnTokC); tokCnt++; + continue; } - /* new word token */ - if (wordTok == tok) { + word_tok: + { + /* try continue with previous word token */ + ClockScanToken *wordTok = tok - 1; + + if (wordTok < scnTok || wordTok->map != &ScnWordTokenMap) { + /* start with new word token */ + wordTok = tok; wordTok->tokWord.start = p; wordTok->map = &ScnWordTokenMap; + } + + do { + if (isspace(UCHAR(*p))) { + fss->scnSpaceCount++; + } + p = Tcl_UtfNext(p); + } while (p < e && *p != '%'); + wordTok->tokWord.end = p; + + if (wordTok == tok) { AllocTokenInChain(tok, scnTok, fss->scnTokC); tokCnt++; } - if (isspace(UCHAR(*p))) { - fss->scnSpaceCount++; + + } - p = TclUtfNext(p); - wordTok->tokWord.end = p; - } + + break; } } @@ -2098,9 +2282,8 @@ ClockGetOrParseScanFormat( fss->scnTok = scnTok; fss->scnTokC = tokCnt; } -done: - Tcl_MutexUnlock(&ClockFmtMutex); + Tcl_MutexUnlock(&ClockFmtMutex); return fss; } @@ -2216,15 +2399,30 @@ ClockScan( if (map->offs) { p = yyInput; x = p + size; if (map->type == CTOKT_INT) { - if (_str2int((int *)(((char *)info) + map->offs), - p, x, sign) != TCL_OK) { + if (size <= 10) { + _str2int_no((int *)(((char *)info) + map->offs), + p, x, sign); + } else { + /* we don't have such large scan tokens at the moment */ goto overflow; + /* currently unused (maxSize of CTOKT_INT tokens <= 10) */ + #if 0 + if (_str2int((int *)(((char *)info) + map->offs), + p, x, sign) != TCL_OK) { + goto overflow; + } + #endif } p = x; } else { - if (_str2wideInt((Tcl_WideInt *)(((char *)info) + map->offs), - p, x, sign) != TCL_OK) { - goto overflow; + if (size <= 18) { + _str2wideInt_no((Tcl_WideInt *)(((char *)info) + map->offs), + p, x, sign); + } else { + if (_str2wideInt((Tcl_WideInt *)(((char *)info) + map->offs), + p, x, sign) != TCL_OK) { + goto overflow; + } } p = x; } @@ -2324,6 +2522,7 @@ ClockScan( /* * Invalidate result */ + flags |= info->flags; /* seconds token (%s) take precedence over all other tokens */ if ((opts->flags & CLF_EXTENDED) || !(flags & CLF_POSIXSEC)) { @@ -2592,6 +2791,67 @@ ClockFmtToken_WeekOfYear_Proc( return TCL_OK; } static int +ClockFmtToken_JDN_Proc( + ClockFmtScnCmdArgs *opts, + DateFormat *dateFmt, + ClockFormatToken *tok, + int *val) + { + Tcl_WideInt intJD = dateFmt->date.julianDay; + int fractJD; + + /* Convert to JDN parts (regarding start offset) and time fraction */ + fractJD = dateFmt->date.secondOfDay + - (int)tok->map->offs; /* 0 for calendar or 43200 for astro JD */ + if (fractJD < 0) { + intJD--; + fractJD += SECONDS_PER_DAY; + } + if (fractJD && intJD < 0) { /* avoid jump over 0, by negative JD's */ + intJD++; + if (intJD == 0) { + /* -0.0 / -0.9 has zero integer part, so append "-" extra */ + if (FrmResultAllocate(dateFmt, 1) != TCL_OK) { return TCL_ERROR; }; + *dateFmt->output++ = '-'; + } + /* and inverse seconds of day, -0(75) -> -0.25 as float */ + fractJD = SECONDS_PER_DAY - fractJD; + } + + /* 21 is max width of (negative) wide-int (rather smaller, but anyway a time fraction below) */ + if (FrmResultAllocate(dateFmt, 21) != TCL_OK) { return TCL_ERROR; }; + dateFmt->output = _witoaw(dateFmt->output, intJD, '0', 1); + /* simplest cases .0 and .5 */ + if (!fractJD || fractJD == (SECONDS_PER_DAY / 2)) { + /* point + 0 or 5 */ + if (FrmResultAllocate(dateFmt, 1+1) != TCL_OK) { return TCL_ERROR; }; + *dateFmt->output++ = '.'; + *dateFmt->output++ = !fractJD ? '0' : '5'; + *dateFmt->output = '\0'; + return TCL_OK; + } else { + /* wrap the time fraction */ + #define JDN_MAX_PRECISION 8 + #define JDN_MAX_PRECBOUND 100000000 /* 10**JDN_MAX_PRECISION */ + char *p; + + /* to float (part after floating point, + 0.5 to round it up) */ + fractJD = (int)( + (double)fractJD * JDN_MAX_PRECBOUND / SECONDS_PER_DAY + 0.5 + ); + /* point + integer (as time fraction after floating point) */ + if (FrmResultAllocate(dateFmt, 1+JDN_MAX_PRECISION) != TCL_OK) { return TCL_ERROR; }; + *dateFmt->output++ = '.'; + p = _itoaw(dateFmt->output, fractJD, '0', JDN_MAX_PRECISION); + /* remove trailing zero's */ + dateFmt->output++; + while (p > dateFmt->output && *(p-1) == '0') {p--;} + *p = '\0'; + dateFmt->output = p; + } + return TCL_OK; +} +static int ClockFmtToken_TimeZone_Proc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, @@ -2801,7 +3061,7 @@ static ClockFormatTokenMap FmtSTokenMap[] = { /* %V */ {CTOKT_INT, "0", 2, 0, 0, 0, TclOffset(DateFormat, date.iso8601Week), NULL}, /* %z %Z */ - {CTOKT_INT, NULL, 0, 0, 0, 0, 0, + {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_TimeZone_Proc, NULL}, /* %g */ {CTOKT_INT, "0", 2, 0, 0, 100, TclOffset(DateFormat, date.iso8601Year), NULL}, @@ -2810,7 +3070,7 @@ static ClockFormatTokenMap FmtSTokenMap[] = { /* %j */ {CTOKT_INT, "0", 3, 0, 0, 0, TclOffset(DateFormat, date.dayOfYear), NULL}, /* %J */ - {CTOKT_WIDE, "0", 1, 0, 0, 0, TclOffset(DateFormat, date.julianDay), NULL}, + {CTOKT_WIDE, "0", 7, 0, 0, 0, TclOffset(DateFormat, date.julianDay), NULL}, /* %s */ {CTOKT_WIDE, "0", 1, 0, 0, 0, TclOffset(DateFormat, date.seconds), NULL}, /* %n */ @@ -2818,7 +3078,7 @@ static ClockFormatTokenMap FmtSTokenMap[] = { /* %t */ {CTOKT_CHAR, "\t", 0, 0, 0, 0, 0, NULL}, /* %Q */ - {CTOKT_INT, NULL, 0, 0, 0, 0, 0, + {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_StarDate_Proc, NULL}, }; static const char *FmtSTokenMapAliasIndex[2] = { @@ -2827,11 +3087,17 @@ static const char *FmtSTokenMapAliasIndex[2] = { }; static const char *FmtETokenMapIndex = - "Eys"; + "EJjys"; static ClockFormatTokenMap FmtETokenMap[] = { /* %EE */ - {CTOKT_INT, NULL, 0, 0, 0, 0, TclOffset(DateFormat, date.era), + {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_LocaleERA_Proc, NULL}, + /* %EJ */ + {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, /* calendar JDN starts at midnight */ + ClockFmtToken_JDN_Proc, NULL}, + /* %Ej */ + {CFMTT_PROC, NULL, 0, 0, 0, 0, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */ + ClockFmtToken_JDN_Proc, NULL}, /* %Ey %EC */ {CTOKT_INT, NULL, 0, 0, 0, 0, TclOffset(DateFormat, date.year), ClockFmtToken_LocaleERAYear_Proc, NULL}, @@ -2983,20 +3249,29 @@ ClockGetOrParseFmtFormat( } break; default: -word_tok: - if (1) { - ClockFormatToken *wordTok = tok; - if (tok > fmtTok && (tok-1)->map == &FmtWordTokenMap) { - wordTok = tok-1; - } - if (wordTok == tok) { + word_tok: + { + /* try continue with previous word token */ + ClockFormatToken *wordTok = tok - 1; + + if (wordTok < fmtTok || wordTok->map != &FmtWordTokenMap) { + /* start with new word token */ + wordTok = tok; wordTok->tokWord.start = p; wordTok->map = &FmtWordTokenMap; - AllocTokenInChain(tok, fmtTok, fss->fmtTokC); tokCnt++; } - p = TclUtfNext(p); + do { + p = Tcl_UtfNext(p); + } while (p < e && *p != '%'); wordTok->tokWord.end = p; - } + + if (wordTok == tok) { + AllocTokenInChain(tok, fmtTok, fss->fmtTokC); tokCnt++; + } + + + + } break; } } @@ -3013,9 +3288,8 @@ ClockGetOrParseFmtFormat( fss->fmtTok = fmtTok; fss->fmtTokC = tokCnt; } -done: - Tcl_MutexUnlock(&ClockFmtMutex); + Tcl_MutexUnlock(&ClockFmtMutex); return fss; } @@ -3199,10 +3473,12 @@ ClockFrmScnClearCaches(void) Tcl_MutexUnlock(&ClockFmtMutex); } -static void -ClockFrmScnFinalize( - ClientData clientData) /* Not used. */ +void +ClockFrmScnFinalize() { + if (!initialized) { + return; + } Tcl_MutexLock(&ClockFmtMutex); #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 /* clear GC */ @@ -3211,8 +3487,8 @@ ClockFrmScnFinalize( ClockFmtScnStorage_GC.count = 0; #endif if (initialized) { - Tcl_DeleteHashTable(&FmtScnHashTable); initialized = 0; + Tcl_DeleteHashTable(&FmtScnHashTable); } Tcl_MutexUnlock(&ClockFmtMutex); Tcl_MutexFinalize(&ClockFmtMutex); diff --git a/generic/tclClockModInt.c b/generic/tclClockModInt.c index b25e99b..91c1f56 100644 --- a/generic/tclClockModInt.c +++ b/generic/tclClockModInt.c @@ -182,8 +182,16 @@ EnvEpochTraceProc( int TclpCompileEnsemblObjCmd( ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + int ensFlags = 0; Tcl_Command token = Tcl_FindCommand(interp, "clock", NULL, TCL_GLOBAL_ONLY); - return Tcl_SetEnsembleFlags(interp, token, ENSEMBLE_COMPILE); + if (!token) { + return TCL_ERROR; + } + if (Tcl_GetEnsembleFlags(interp, token, &ensFlags) != TCL_OK) { + return TCL_ERROR; + } + ensFlags |= ENSEMBLE_COMPILE; + return Tcl_SetEnsembleFlags(interp, token, ensFlags); } diff --git a/generic/tclClockModInt.h b/generic/tclClockModInt.h index b767e8a..82c252d 100644 --- a/generic/tclClockModInt.h +++ b/generic/tclClockModInt.h @@ -21,6 +21,14 @@ # define inline __inline #endif +/* + * several tclInt.h internals (tcl8.6 version depending): + */ + +#ifndef TclUtfNext +#define TclUtfNext(src) \ + ( (((unsigned char) *(src)) < 0xC0) ? src + 1 : Tcl_UtfNext(src) ) +#endif /* * Signal using modified tcl version (dict smartref's, etc.) diff --git a/generic/tclDate.c b/generic/tclDate.c index 165f911..6531514 100644 --- a/generic/tclDate.c +++ b/generic/tclDate.c @@ -1,8 +1,9 @@ -/* A Bison parser, made by GNU Bison 3.1. */ +/* A Bison parser, made by GNU Bison 3.8.2. */ /* Bison implementation for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,7 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . */ + along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work @@ -33,6 +34,10 @@ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. @@ -40,11 +45,11 @@ define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ -/* Identify Bison output. */ -#define YYBISON 1 +/* Identify Bison output, and Bison version. */ +#define YYBISON 30802 -/* Bison version. */ -#define YYBISON_VERSION "3.1" +/* Bison version string. */ +#define YYBISON_VERSION "3.8.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" @@ -66,9 +71,7 @@ #define yydebug TclDatedebug #define yynerrs TclDatenerrs - -/* Copy the first part of user declarations. */ - +/* First part of user prologue. */ /* * tclDate.c -- @@ -76,8 +79,9 @@ * This file is generated from a yacc grammar defined in the file * tclGetDate.y. It should not be edited directly. * - * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans. - * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. + * Copyright © 1995-1997 Sun Microsystems, Inc. + * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -119,18 +123,24 @@ #define TM_YEAR_BASE 1900 -#define HOUR(x) ((int) (60 * x)) -#define SECSPERDAY (24L * 60L * 60L) -#define IsLeapYear(x) ((x % 4 == 0) && (x % 100 != 0 || x % 400 == 0)) +#define HOUR(x) ((60 * (int)(x))) +#define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0)) + +#define yyIncrFlags(f) \ + do { \ + info->errFlags |= (info->flags & (f)); \ + if (info->errFlags) { YYABORT; } \ + info->flags |= (f); \ + } while (0); /* * An entry in the lexical lookup table. */ -typedef struct _TABLE { +typedef struct { const char *name; int type; - time_t value; + int value; } TABLE; /* @@ -143,23 +153,27 @@ typedef enum _DSTMODE { - +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif # ifndef YY_NULLPTR -# if defined __cplusplus && 201103L <= __cplusplus -# define YY_NULLPTR nullptr +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif # else -# define YY_NULLPTR 0 +# define YY_NULLPTR ((void*)0) # endif # endif -/* Enabling verbose error messages. */ -#ifdef YYERROR_VERBOSE -# undef YYERROR_VERBOSE -# define YYERROR_VERBOSE 1 -#else -# define YYERROR_VERBOSE 0 -#endif - /* Debug traces. */ #ifndef YYDEBUG @@ -169,46 +183,50 @@ typedef enum _DSTMODE { extern int TclDatedebug; #endif -/* Token type. */ +/* Token kinds. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { - tAGO = 258, - tDAY = 259, - tDAYZONE = 260, - tID = 261, - tMERIDIAN = 262, - tMONTH = 263, - tMONTH_UNIT = 264, - tSTARDATE = 265, - tSEC_UNIT = 266, - tUNUMBER = 267, - tZONE = 268, - tZONEwO4 = 269, - tZONEwO2 = 270, - tEPOCH = 271, - tDST = 272, - tISOBASE = 273, - tDAY_UNIT = 274, - tNEXT = 275, - SP = 276 + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + tAGO = 258, /* tAGO */ + tDAY = 259, /* tDAY */ + tDAYZONE = 260, /* tDAYZONE */ + tID = 261, /* tID */ + tMERIDIAN = 262, /* tMERIDIAN */ + tMONTH = 263, /* tMONTH */ + tMONTH_UNIT = 264, /* tMONTH_UNIT */ + tSTARDATE = 265, /* tSTARDATE */ + tSEC_UNIT = 266, /* tSEC_UNIT */ + tUNUMBER = 267, /* tUNUMBER */ + tZONE = 268, /* tZONE */ + tZONEwO4 = 269, /* tZONEwO4 */ + tZONEwO2 = 270, /* tZONEwO2 */ + tEPOCH = 271, /* tEPOCH */ + tDST = 272, /* tDST */ + tISOBAS8 = 273, /* tISOBAS8 */ + tISOBAS6 = 274, /* tISOBAS6 */ + tISOBASL = 275, /* tISOBASL */ + tDAY_UNIT = 276, /* tDAY_UNIT */ + tNEXT = 277, /* tNEXT */ + SP = 278 /* SP */ }; + typedef enum yytokentype yytoken_kind_t; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED - union YYSTYPE { - - time_t Number; + Tcl_WideInt Number; enum _MERIDIAN Meridian; }; - typedef union YYSTYPE YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 @@ -230,22 +248,84 @@ struct YYLTYPE + int TclDateparse (DateInfo* info); -/* Copy the second part of user declarations. */ +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_tAGO = 3, /* tAGO */ + YYSYMBOL_tDAY = 4, /* tDAY */ + YYSYMBOL_tDAYZONE = 5, /* tDAYZONE */ + YYSYMBOL_tID = 6, /* tID */ + YYSYMBOL_tMERIDIAN = 7, /* tMERIDIAN */ + YYSYMBOL_tMONTH = 8, /* tMONTH */ + YYSYMBOL_tMONTH_UNIT = 9, /* tMONTH_UNIT */ + YYSYMBOL_tSTARDATE = 10, /* tSTARDATE */ + YYSYMBOL_tSEC_UNIT = 11, /* tSEC_UNIT */ + YYSYMBOL_tUNUMBER = 12, /* tUNUMBER */ + YYSYMBOL_tZONE = 13, /* tZONE */ + YYSYMBOL_tZONEwO4 = 14, /* tZONEwO4 */ + YYSYMBOL_tZONEwO2 = 15, /* tZONEwO2 */ + YYSYMBOL_tEPOCH = 16, /* tEPOCH */ + YYSYMBOL_tDST = 17, /* tDST */ + YYSYMBOL_tISOBAS8 = 18, /* tISOBAS8 */ + YYSYMBOL_tISOBAS6 = 19, /* tISOBAS6 */ + YYSYMBOL_tISOBASL = 20, /* tISOBASL */ + YYSYMBOL_tDAY_UNIT = 21, /* tDAY_UNIT */ + YYSYMBOL_tNEXT = 22, /* tNEXT */ + YYSYMBOL_SP = 23, /* SP */ + YYSYMBOL_24_ = 24, /* ':' */ + YYSYMBOL_25_ = 25, /* ',' */ + YYSYMBOL_26_ = 26, /* '-' */ + YYSYMBOL_27_ = 27, /* '/' */ + YYSYMBOL_28_T_ = 28, /* 'T' */ + YYSYMBOL_29_ = 29, /* '.' */ + YYSYMBOL_30_ = 30, /* '+' */ + YYSYMBOL_YYACCEPT = 31, /* $accept */ + YYSYMBOL_spec = 32, /* spec */ + YYSYMBOL_item = 33, /* item */ + YYSYMBOL_iextime = 34, /* iextime */ + YYSYMBOL_time = 35, /* time */ + YYSYMBOL_zone = 36, /* zone */ + YYSYMBOL_comma = 37, /* comma */ + YYSYMBOL_day = 38, /* day */ + YYSYMBOL_iexdate = 39, /* iexdate */ + YYSYMBOL_date = 40, /* date */ + YYSYMBOL_ordMonth = 41, /* ordMonth */ + YYSYMBOL_isosep = 42, /* isosep */ + YYSYMBOL_isodate = 43, /* isodate */ + YYSYMBOL_isotime = 44, /* isotime */ + YYSYMBOL_iso = 45, /* iso */ + YYSYMBOL_trek = 46, /* trek */ + YYSYMBOL_relspec = 47, /* relspec */ + YYSYMBOL_relunits = 48, /* relunits */ + YYSYMBOL_sign = 49, /* sign */ + YYSYMBOL_unit = 50, /* unit */ + YYSYMBOL_INTNUM = 51, /* INTNUM */ + YYSYMBOL_numitem = 52, /* numitem */ + YYSYMBOL_o_merid = 53 /* o_merid */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; +/* Second part of user prologue. */ + /* * Prototypes of internal functions. */ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); - static void TclDateerror(YYLTYPE* location, +static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); - static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, +static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo* info); MODULE_SCOPE int yyparse(DateInfo*); @@ -256,28 +336,87 @@ MODULE_SCOPE int yyparse(DateInfo*); # undef short #endif -#ifdef YYTYPE_UINT8 -typedef YYTYPE_UINT8 yytype_uint8; -#else -typedef unsigned char yytype_uint8; +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif #endif -#ifdef YYTYPE_INT8 -typedef YYTYPE_INT8 yytype_int8; +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif -#ifdef YYTYPE_UINT16 -typedef YYTYPE_UINT16 yytype_uint16; +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; #else -typedef unsigned short yytype_uint16; +typedef short yytype_int16; +#endif + +/* Work around bug in HP-UX 11.23, which defines these macros + incorrectly for preprocessor constants. This workaround can likely + be removed in 2023, as HPE has promised support for HP-UX 11.23 + (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of + . */ +#ifdef __hpux +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 #endif -#ifdef YYTYPE_INT16 -typedef YYTYPE_INT16 yytype_int16; +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; #else -typedef short yytype_int16; +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif #endif #ifndef YYSIZE_T @@ -285,7 +424,7 @@ typedef short yytype_int16; # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t -# elif ! defined YYSIZE_T +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else @@ -293,7 +432,20 @@ typedef short yytype_int16; # endif #endif -#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_int8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS @@ -307,47 +459,43 @@ typedef short yytype_int16; # endif #endif -#ifndef YY_ATTRIBUTE -# if (defined __GNUC__ \ - && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ - || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C -# define YY_ATTRIBUTE(Spec) __attribute__(Spec) -# else -# define YY_ATTRIBUTE(Spec) /* empty */ -# endif -#endif #ifndef YY_ATTRIBUTE_PURE -# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif #endif #ifndef YY_ATTRIBUTE_UNUSED -# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) -#endif - -#if !defined _Noreturn \ - && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) -# if defined _MSC_VER && 1200 <= _MSC_VER -# define _Noreturn __declspec (noreturn) +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else -# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ -# define YYUSE(E) ((void) (E)) +# define YY_USE(E) ((void) (E)) #else -# define YYUSE(E) /* empty */ +# define YY_USE(E) /* empty */ #endif -#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ -# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ - _Pragma ("GCC diagnostic push") \ - _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ +#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ +# if __GNUC__ * 100 + __GNUC_MINOR__ < 407 +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") +# else +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") -# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ +# endif +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value @@ -360,8 +508,22 @@ typedef short yytype_int16; # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) -#if ! defined yyoverflow || YYERROR_VERBOSE +#if !defined yyoverflow /* The parser invokes alloca or malloc; define the necessary symbols. */ @@ -426,8 +588,7 @@ void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif -#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ - +#endif /* !defined yyoverflow */ #if (! defined yyoverflow \ && (! defined __cplusplus \ @@ -437,18 +598,19 @@ void free (void *); /* INFRINGES ON USER NAME SPACE */ /* A type that is properly aligned for any stack member. */ union yyalloc { - yytype_int16 yyss_alloc; + yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; YYLTYPE yyls_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ -# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ - ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE) \ + + YYSIZEOF (YYLTYPE)) \ + 2 * YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 @@ -461,11 +623,11 @@ union yyalloc # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ - YYSIZE_T yynewbytes; \ + YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ - yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ - yyptr += yynewbytes / sizeof (*yyptr); \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) @@ -477,12 +639,12 @@ union yyalloc # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ - __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ - YYSIZE_T yyi; \ + YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ @@ -494,38 +656,41 @@ union yyalloc /* YYFINAL -- State number of the termination state. */ #define YYFINAL 2 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 116 +#define YYLAST 98 /* YYNTOKENS -- Number of terminals. */ -#define YYNTOKENS 28 +#define YYNTOKENS 31 /* YYNNTS -- Number of nonterminals. */ -#define YYNNTS 18 +#define YYNNTS 23 /* YYNRULES -- Number of rules. */ -#define YYNRULES 66 +#define YYNRULES 72 /* YYNSTATES -- Number of states. */ -#define YYNSTATES 106 +#define YYNSTATES 103 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 278 -/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned - by yylex, with out-of-bounds checking. */ -#define YYUNDEFTOK 2 -#define YYMAXUTOK 276 -#define YYTRANSLATE(YYX) \ - ((unsigned) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM - as returned by yylex, without out-of-bounds checking. */ -static const yytype_uint8 yytranslate[] = + as returned by yylex. */ +static const yytype_int8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 27, 23, 25, 26, 24, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 22, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 30, 25, 26, 29, 27, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 24, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -544,218 +709,222 @@ static const yytype_uint8 yytranslate[] = 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21 + 15, 16, 17, 18, 19, 20, 21, 22, 23 }; #if YYDEBUG - /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ -static const yytype_uint16 yyrline[] = +/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_int16 yyrline[] = { - 0, 160, 160, 161, 162, 165, 168, 171, 174, 177, - 180, 183, 187, 192, 195, 201, 207, 215, 219, 223, - 227, 231, 235, 241, 242, 245, 250, 255, 260, 265, - 270, 277, 281, 286, 291, 296, 301, 305, 310, 314, - 319, 326, 330, 336, 345, 353, 361, 370, 380, 394, - 399, 402, 405, 408, 411, 414, 417, 422, 425, 430, - 434, 438, 444, 447, 452, 470, 473 + 0, 171, 171, 172, 176, 179, 182, 185, 188, 191, + 194, 197, 201, 204, 209, 215, 221, 226, 230, 234, + 238, 242, 246, 252, 253, 256, 260, 264, 268, 272, + 276, 282, 288, 292, 297, 298, 303, 307, 312, 316, + 321, 328, 332, 338, 338, 340, 345, 350, 352, 357, + 359, 360, 368, 379, 393, 398, 401, 404, 407, 410, + 413, 416, 421, 424, 429, 433, 437, 443, 446, 449, + 454, 472, 475 }; #endif -#if YYDEBUG || YYERROR_VERBOSE || 0 +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if YYDEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { - "$end", "error", "$undefined", "tAGO", "tDAY", "tDAYZONE", "tID", - "tMERIDIAN", "tMONTH", "tMONTH_UNIT", "tSTARDATE", "tSEC_UNIT", - "tUNUMBER", "tZONE", "tZONEwO4", "tZONEwO2", "tEPOCH", "tDST", - "tISOBASE", "tDAY_UNIT", "tNEXT", "SP", "':'", "','", "'/'", "'-'", - "'.'", "'+'", "$accept", "spec", "item", "time", "zone", "comma", "day", - "date", "ordMonth", "iso", "trek", "relspec", "relunits", "sign", "unit", - "INTNUM", "number", "o_merid", YY_NULLPTR + "\"end of file\"", "error", "\"invalid token\"", "tAGO", "tDAY", + "tDAYZONE", "tID", "tMERIDIAN", "tMONTH", "tMONTH_UNIT", "tSTARDATE", + "tSEC_UNIT", "tUNUMBER", "tZONE", "tZONEwO4", "tZONEwO2", "tEPOCH", + "tDST", "tISOBAS8", "tISOBAS6", "tISOBASL", "tDAY_UNIT", "tNEXT", "SP", + "':'", "','", "'-'", "'/'", "'T'", "'.'", "'+'", "$accept", "spec", + "item", "iextime", "time", "zone", "comma", "day", "iexdate", "date", + "ordMonth", "isosep", "isodate", "isotime", "iso", "trek", "relspec", + "relunits", "sign", "unit", "INTNUM", "numitem", "o_merid", YY_NULLPTR }; -#endif -# ifdef YYPRINT -/* YYTOKNUM[NUM] -- (External) token number corresponding to the - (internal) symbol number NUM (which must be that of a token). */ -static const yytype_uint16 yytoknum[] = +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) { - 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, - 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, - 275, 276, 58, 44, 47, 45, 46, 43 -}; -# endif + return yytname[yysymbol]; +} +#endif -#define YYPACT_NINF -17 +#define YYPACT_NINF (-21) -#define yypact_value_is_default(Yystate) \ - (!!((Yystate) == (-17))) +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) -#define YYTABLE_NINF -1 +#define YYTABLE_NINF (-68) -#define yytable_value_is_error(Yytable_value) \ +#define yytable_value_is_error(Yyn) \ 0 - /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing - STATE-NUM. */ +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ static const yytype_int8 yypact[] = { - -17, 48, -17, -9, -17, 34, -17, 19, -17, -2, - 30, -10, -10, -17, 8, -17, 0, 72, -17, -17, - -17, -17, -17, -17, -17, -17, -17, -17, -17, 52, - 18, -17, 16, -17, 49, -17, -9, -17, -17, 25, - -17, -17, 59, 60, 62, -5, -17, 19, 19, 20, - -17, 31, -17, -17, 70, -17, 16, -17, -17, 75, - 32, 16, -17, -17, 77, 81, -17, 6, 71, 69, - 73, -17, -17, 74, -17, 78, -17, -17, -17, -17, - 97, 16, -17, -17, -17, -17, 90, -17, 91, 92, - 93, 94, 95, -17, -17, 101, -17, -17, -17, 87, - 88, -17, 99, 100, -17, -17 + -21, 11, -21, -20, -21, 5, -21, -9, -21, 46, + 17, 9, 9, -21, -21, -21, 24, -21, 57, -21, + -21, -21, 33, -21, -21, -21, -21, -21, -21, -15, + -21, -21, -21, 45, 26, -21, -7, -21, 51, -21, + -20, -21, -21, -21, 48, -21, -21, 67, 68, 52, + 69, -21, -9, -9, -21, -21, -21, -21, 74, -21, + -7, -21, -21, -21, -21, 44, -21, 79, 40, -7, + -21, -21, 72, 73, -21, 62, 61, 63, 64, -21, + -21, -21, -21, 66, -21, -21, -21, -21, 84, -7, + -21, -21, -21, 80, 81, 82, 83, -21, -21, -21, + -21, -21, -21 }; - /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. - Performed when YYTABLE does not specify something else to do. Zero - means the default is an error. */ -static const yytype_uint8 yydefact[] = +/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = { - 2, 0, 1, 25, 19, 0, 61, 0, 59, 62, - 18, 0, 0, 39, 33, 60, 0, 0, 57, 58, - 3, 5, 6, 9, 7, 8, 11, 12, 10, 50, - 0, 56, 64, 13, 23, 26, 36, 62, 63, 0, - 27, 14, 38, 0, 0, 0, 17, 0, 0, 0, - 44, 0, 30, 41, 62, 54, 0, 4, 49, 62, - 0, 22, 53, 24, 0, 0, 40, 65, 31, 0, - 0, 20, 21, 0, 43, 0, 47, 42, 55, 29, - 62, 0, 52, 37, 48, 66, 0, 15, 0, 0, - 0, 0, 0, 28, 51, 65, 32, 34, 35, 0, - 0, 16, 0, 0, 46, 45 + 2, 0, 1, 25, 19, 0, 66, 0, 64, 70, + 18, 0, 0, 39, 45, 46, 0, 65, 0, 62, + 63, 3, 71, 4, 5, 8, 47, 6, 7, 34, + 10, 11, 9, 55, 0, 61, 0, 12, 23, 26, + 36, 67, 69, 68, 0, 27, 15, 38, 0, 0, + 0, 17, 0, 0, 52, 51, 30, 41, 67, 59, + 0, 72, 16, 44, 43, 0, 54, 67, 0, 22, + 58, 24, 0, 0, 40, 14, 0, 0, 32, 20, + 21, 42, 60, 0, 48, 49, 50, 29, 67, 0, + 57, 37, 53, 0, 0, 0, 0, 28, 56, 13, + 35, 31, 33 }; - /* YYPGOTO[NTERM-NUM]. */ +/* YYPGOTO[NTERM-NUM]. */ static const yytype_int8 yypgoto[] = { - -17, -17, 96, -17, -17, 79, -17, -17, -17, -17, - -17, -17, -17, 22, -16, -6, -17, 21 + -21, -21, -21, 31, -21, -21, 58, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -5, -18, + -6, -21, -21 }; - /* YYDEFGOTO[NTERM-NUM]. */ +/* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int8 yydefgoto[] = { - -1, 1, 20, 21, 22, 35, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 87 + 0, 1, 21, 22, 23, 24, 39, 25, 26, 27, + 28, 65, 29, 86, 30, 31, 32, 33, 34, 35, + 36, 37, 62 }; - /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If - positive, shift that token. If negative, reduce the rule whose - number is the opposite. If YYTABLE_NINF, syntax error. */ -static const yytype_uint8 yytable[] = +/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int8 yytable[] = { - 55, 39, 40, 69, 52, 41, 42, 70, 53, 6, - 56, 8, 54, 85, 34, 18, 62, 19, 38, 15, - 43, 49, 44, 45, 61, 6, 50, 8, 86, 51, - 59, 37, 73, 47, 48, 15, 38, 38, 74, 60, - 78, 71, 72, 75, 80, 82, 36, 46, 2, 76, - 38, 65, 3, 4, 81, 58, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 94, 14, 15, 16, 17, - 63, 66, 67, 18, 68, 19, 3, 4, 77, 79, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 83, - 14, 15, 16, 84, 89, 88, 91, 18, 90, 19, - 92, 93, 95, 96, 97, 98, 99, 100, 85, 102, - 103, 104, 105, 57, 0, 64, 101 + 59, 44, 6, 41, 8, 38, 52, 53, 63, 42, + 43, 2, 60, 64, 17, 3, 4, 40, 70, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 69, 14, + 15, 16, 17, 18, 51, 19, 54, 19, 67, 20, + 61, 20, 82, 55, 42, 43, 79, 80, 66, 68, + 45, 90, 88, 46, 47, -67, 83, -67, 42, 43, + 76, 56, 89, 84, 77, 57, 6, -67, 8, 58, + 48, 98, 49, 50, 71, 42, 43, 73, 17, 74, + 75, 78, 81, 87, 91, 92, 93, 94, 97, 95, + 48, 96, 99, 100, 101, 102, 85, 0, 72 }; static const yytype_int8 yycheck[] = { - 16, 7, 4, 8, 4, 7, 8, 12, 8, 9, - 16, 11, 12, 7, 23, 25, 32, 27, 18, 19, - 22, 13, 24, 25, 30, 9, 18, 11, 22, 21, - 12, 12, 12, 11, 12, 19, 18, 18, 18, 21, - 56, 47, 48, 12, 12, 61, 12, 17, 0, 18, - 18, 26, 4, 5, 60, 3, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 81, 18, 19, 20, 21, - 21, 12, 12, 25, 12, 27, 4, 5, 8, 4, - 8, 9, 10, 11, 12, 13, 14, 15, 16, 12, - 18, 19, 20, 12, 25, 24, 22, 25, 25, 27, - 22, 4, 12, 12, 12, 12, 12, 12, 7, 22, - 22, 12, 12, 17, -1, 36, 95 + 18, 7, 9, 12, 11, 25, 11, 12, 23, 18, + 19, 0, 18, 28, 21, 4, 5, 12, 36, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 34, 18, + 19, 20, 21, 22, 17, 26, 12, 26, 12, 30, + 7, 30, 60, 19, 18, 19, 52, 53, 3, 23, + 4, 69, 12, 7, 8, 9, 12, 11, 18, 19, + 8, 4, 68, 19, 12, 8, 9, 21, 11, 12, + 24, 89, 26, 27, 23, 18, 19, 29, 21, 12, + 12, 12, 8, 4, 12, 12, 24, 26, 4, 26, + 24, 27, 12, 12, 12, 12, 65, -1, 40 }; - /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing - symbol of state STATE-NUM. */ -static const yytype_uint8 yystos[] = +/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + state STATE-NUM. */ +static const yytype_int8 yystos[] = { - 0, 29, 0, 4, 5, 8, 9, 10, 11, 12, - 13, 14, 15, 16, 18, 19, 20, 21, 25, 27, - 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 23, 33, 12, 12, 18, 43, - 4, 7, 8, 22, 24, 25, 17, 41, 41, 13, - 18, 21, 4, 8, 12, 42, 43, 30, 3, 12, - 21, 43, 42, 21, 33, 26, 12, 12, 12, 8, - 12, 43, 43, 12, 18, 12, 18, 8, 42, 4, - 12, 43, 42, 12, 12, 7, 22, 45, 24, 25, - 25, 22, 22, 4, 42, 12, 12, 12, 12, 12, - 12, 45, 22, 22, 12, 12 + 0, 32, 0, 4, 5, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 18, 19, 20, 21, 22, 26, + 30, 33, 34, 35, 36, 38, 39, 40, 41, 43, + 45, 46, 47, 48, 49, 50, 51, 52, 25, 37, + 12, 12, 18, 19, 51, 4, 7, 8, 24, 26, + 27, 17, 49, 49, 12, 19, 4, 8, 12, 50, + 51, 7, 53, 23, 28, 42, 3, 12, 23, 51, + 50, 23, 37, 29, 12, 12, 8, 12, 12, 51, + 51, 8, 50, 12, 19, 34, 44, 4, 12, 51, + 50, 12, 12, 24, 26, 26, 27, 4, 50, 12, + 12, 12, 12 }; - /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ -static const yytype_uint8 yyr1[] = +/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr1[] = { - 0, 28, 29, 29, 29, 30, 30, 30, 30, 30, - 30, 30, 30, 30, 31, 31, 31, 32, 32, 32, - 32, 32, 32, 33, 33, 34, 34, 34, 34, 34, - 34, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 36, 36, 37, 37, 37, 37, 37, 38, 39, - 39, 40, 40, 40, 40, 40, 40, 41, 41, 42, - 42, 42, 43, 43, 44, 45, 45 + 0, 31, 32, 32, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 34, 34, 35, 35, 36, 36, 36, + 36, 36, 36, 37, 37, 38, 38, 38, 38, 38, + 38, 39, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 41, 41, 42, 42, 43, 43, 43, 44, 44, + 45, 45, 45, 46, 47, 47, 48, 48, 48, 48, + 48, 48, 49, 49, 50, 50, 50, 51, 51, 51, + 52, 53, 53 }; - /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ -static const yytype_uint8 yyr2[] = +/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr2[] = { - 0, 2, 0, 2, 3, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 2, 4, 6, 2, 1, 1, + 0, 2, 0, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 5, 3, 2, 2, 2, 1, 1, 3, 3, 2, 1, 2, 1, 2, 2, 4, 3, - 2, 3, 5, 1, 5, 5, 2, 4, 2, 1, - 3, 2, 3, 3, 2, 7, 7, 3, 4, 2, - 1, 4, 3, 2, 2, 3, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 1 + 2, 5, 3, 5, 1, 5, 2, 4, 2, 1, + 3, 2, 3, 1, 1, 1, 1, 1, 1, 1, + 3, 2, 2, 4, 2, 1, 4, 3, 2, 2, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1 }; +enum { YYENOMEM = -2 }; + #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) -#define YYEMPTY (-2) -#define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab +#define YYNOMEM goto yyexhaustedlab #define YYRECOVERING() (!!yyerrstatus) -#define YYBACKUP(Token, Value) \ -do \ - if (yychar == YYEMPTY) \ - { \ - yychar = (Token); \ - yylval = (Value); \ - YYPOPSTACK (yylen); \ - yystate = *yyssp; \ - goto yybackup; \ - } \ - else \ - { \ - yyerror (&yylloc, info, YY_("syntax error: cannot back up")); \ - YYERROR; \ - } \ -while (0) - -/* Error token number */ -#define YYTERROR 1 -#define YYERRCODE 256 - +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, info, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use YYerror or YYUNDEF. */ +#define YYERRCODE YYUNDEF /* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. If N is 0, then set CURRENT to the empty location which ends @@ -799,20 +968,27 @@ do { \ } while (0) -/* YY_LOCATION_PRINT -- Print the location on the stream. +/* YYLOCATION_PRINT -- Print the location on the stream. This macro was not mandated originally: define only if we know we won't break user code: when these are the locations we know. */ -#ifndef YY_LOCATION_PRINT -# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# ifndef YYLOCATION_PRINT + +# if defined YY_LOCATION_PRINT + + /* Temporary convenience wrapper in case some people defined the + undocumented and private YY_LOCATION_PRINT macros. */ +# define YYLOCATION_PRINT(File, Loc) YY_LOCATION_PRINT(File, *(Loc)) + +# elif defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL /* Print *YYLOCP on YYO. Private, do not rely on its existence. */ YY_ATTRIBUTE_UNUSED -static unsigned +static int yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) { - unsigned res = 0; + int res = 0; int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; if (0 <= yylocp->first_line) { @@ -832,64 +1008,72 @@ yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) res += YYFPRINTF (yyo, "-%d", end_col); } return res; - } +} -# define YY_LOCATION_PRINT(File, Loc) \ - yy_location_print_ (File, &(Loc)) +# define YYLOCATION_PRINT yy_location_print_ -# else -# define YY_LOCATION_PRINT(File, Loc) ((void) 0) -# endif -#endif + /* Temporary convenience wrapper in case some people defined the + undocumented and private YY_LOCATION_PRINT macros. */ +# define YY_LOCATION_PRINT(File, Loc) YYLOCATION_PRINT(File, &(Loc)) + +# else + +# define YYLOCATION_PRINT(File, Loc) ((void) 0) + /* Temporary convenience wrapper in case some people defined the + undocumented and private YY_LOCATION_PRINT macros. */ +# define YY_LOCATION_PRINT YYLOCATION_PRINT + +# endif +# endif /* !defined YYLOCATION_PRINT */ -# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ - Type, Value, Location, info); \ + Kind, Value, Location, info); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) -/*----------------------------------------. -| Print this symbol's value on YYOUTPUT. | -`----------------------------------------*/ +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ static void -yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { - FILE *yyo = yyoutput; - YYUSE (yyo); - YYUSE (yylocationp); - YYUSE (info); + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (yylocationp); + YY_USE (info); if (!yyvaluep) return; -# ifdef YYPRINT - if (yytype < YYNTOKENS) - YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); -# endif - YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END } -/*--------------------------------. -| Print this symbol on YYOUTPUT. | -`--------------------------------*/ +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ static void -yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { - YYFPRINTF (yyoutput, "%s %s (", - yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); - YY_LOCATION_PRINT (yyoutput, *yylocationp); - YYFPRINTF (yyoutput, ": "); - yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, info); - YYFPRINTF (yyoutput, ")"); + YYLOCATION_PRINT (yyo, yylocationp); + YYFPRINTF (yyo, ": "); + yy_symbol_value_print (yyo, yykind, yyvaluep, yylocationp, info); + YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. @@ -898,7 +1082,7 @@ yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYL `------------------------------------------------------------------*/ static void -yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) @@ -921,21 +1105,22 @@ do { \ `------------------------------------------------*/ static void -yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, DateInfo* info) +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, + int yyrule, DateInfo* info) { - unsigned long yylno = yyrline[yyrule]; + int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; - YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, - yystos[yyssp[yyi + 1 - yynrhs]], - &(yyvsp[(yyi + 1) - (yynrhs)]) - , &(yylsp[(yyi + 1) - (yynrhs)]) , info); + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], + &(yylsp[(yyi + 1) - (yynrhs)]), info); YYFPRINTF (stderr, "\n"); } } @@ -950,8 +1135,8 @@ do { \ multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ -# define YYDPRINTF(Args) -# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ @@ -974,251 +1159,35 @@ int yydebug; #endif -#if YYERROR_VERBOSE - -# ifndef yystrlen -# if defined __GLIBC__ && defined _STRING_H -# define yystrlen strlen -# else -/* Return the length of YYSTR. */ -static YYSIZE_T -yystrlen (const char *yystr) -{ - YYSIZE_T yylen; - for (yylen = 0; yystr[yylen]; yylen++) - continue; - return yylen; -} -# endif -# endif - -# ifndef yystpcpy -# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE -# define yystpcpy stpcpy -# else -/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in - YYDEST. */ -static char * -yystpcpy (char *yydest, const char *yysrc) -{ - char *yyd = yydest; - const char *yys = yysrc; - - while ((*yyd++ = *yys++) != '\0') - continue; - - return yyd - 1; -} -# endif -# endif - -# ifndef yytnamerr -/* Copy to YYRES the contents of YYSTR after stripping away unnecessary - quotes and backslashes, so that it's suitable for yyerror. The - heuristic is that double-quoting is unnecessary unless the string - contains an apostrophe, a comma, or backslash (other than - backslash-backslash). YYSTR is taken from yytname. If YYRES is - null, do not copy; instead, return the length of what the result - would have been. */ -static YYSIZE_T -yytnamerr (char *yyres, const char *yystr) -{ - if (*yystr == '"') - { - YYSIZE_T yyn = 0; - char const *yyp = yystr; - - for (;;) - switch (*++yyp) - { - case '\'': - case ',': - goto do_not_strip_quotes; - - case '\\': - if (*++yyp != '\\') - goto do_not_strip_quotes; - /* Fall through. */ - default: - if (yyres) - yyres[yyn] = *yyp; - yyn++; - break; - - case '"': - if (yyres) - yyres[yyn] = '\0'; - return yyn; - } - do_not_strip_quotes: ; - } - - if (! yyres) - return yystrlen (yystr); - - return yystpcpy (yyres, yystr) - yyres; -} -# endif - -/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message - about the unexpected token YYTOKEN for the state stack whose top is - YYSSP. - - Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is - not large enough to hold the message. In that case, also set - *YYMSG_ALLOC to the required number of bytes. Return 2 if the - required number of bytes is too large to store. */ -static int -yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, - yytype_int16 *yyssp, int yytoken) -{ - YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); - YYSIZE_T yysize = yysize0; - enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; - /* Internationalized format string. */ - const char *yyformat = YY_NULLPTR; - /* Arguments of yyformat. */ - char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; - /* Number of reported tokens (one for the "unexpected", one per - "expected"). */ - int yycount = 0; - - /* There are many possibilities here to consider: - - If this state is a consistent state with a default action, then - the only way this function was invoked is if the default action - is an error action. In that case, don't check for expected - tokens because there are none. - - The only way there can be no lookahead present (in yychar) is if - this state is a consistent state with a default action. Thus, - detecting the absence of a lookahead is sufficient to determine - that there is no unexpected or expected token to report. In that - case, just report a simple "syntax error". - - Don't assume there isn't a lookahead just because this state is a - consistent state with a default action. There might have been a - previous inconsistent state, consistent state with a non-default - action, or user semantic action that manipulated yychar. - - Of course, the expected token list depends on states to have - correct lookahead information, and it depends on the parser not - to perform extra reductions after fetching a lookahead from the - scanner and before detecting a syntax error. Thus, state merging - (from LALR or IELR) and default reductions corrupt the expected - token list. However, the list is correct for canonical LR with - one exception: it will still contain any token that will not be - accepted due to an error action in a later state. - */ - if (yytoken != YYEMPTY) - { - int yyn = yypact[*yyssp]; - yyarg[yycount++] = yytname[yytoken]; - if (!yypact_value_is_default (yyn)) - { - /* Start YYX at -YYN if negative to avoid negative indexes in - YYCHECK. In other words, skip the first -YYN actions for - this state because they are default actions. */ - int yyxbegin = yyn < 0 ? -yyn : 0; - /* Stay within bounds of both yycheck and yytname. */ - int yychecklim = YYLAST - yyn + 1; - int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; - int yyx; - - for (yyx = yyxbegin; yyx < yyxend; ++yyx) - if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR - && !yytable_value_is_error (yytable[yyx + yyn])) - { - if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) - { - yycount = 1; - yysize = yysize0; - break; - } - yyarg[yycount++] = yytname[yyx]; - { - YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); - if (! (yysize <= yysize1 - && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) - return 2; - yysize = yysize1; - } - } - } - } - - switch (yycount) - { -# define YYCASE_(N, S) \ - case N: \ - yyformat = S; \ - break - default: /* Avoid compiler warnings. */ - YYCASE_(0, YY_("syntax error")); - YYCASE_(1, YY_("syntax error, unexpected %s")); - YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); - YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); - YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); - YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); -# undef YYCASE_ - } - { - YYSIZE_T yysize1 = yysize + yystrlen (yyformat); - if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) - return 2; - yysize = yysize1; - } - if (*yymsg_alloc < yysize) - { - *yymsg_alloc = 2 * yysize; - if (! (yysize <= *yymsg_alloc - && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) - *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; - return 1; - } - /* Avoid sprintf, as that infringes on the user's name space. - Don't have undefined behavior even if the translation - produced a string with the wrong number of "%s"s. */ - { - char *yyp = *yymsg; - int yyi = 0; - while ((*yyp = *yyformat) != '\0') - if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) - { - yyp += yytnamerr (yyp, yyarg[yyi++]); - yyformat += 2; - } - else - { - yyp++; - yyformat++; - } - } - return 0; -} -#endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void -yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, DateInfo* info) +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, DateInfo* info) { - YYUSE (yyvaluep); - YYUSE (yylocationp); - YYUSE (info); + YY_USE (yyvaluep); + YY_USE (yylocationp); + YY_USE (info); if (!yymsg) yymsg = "Deleting"; - YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN - YYUSE (yytype); + YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } + + /*----------. | yyparse. | `----------*/ @@ -1226,7 +1195,7 @@ yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocatio int yyparse (DateInfo* info) { -/* The lookahead symbol. */ +/* Lookahead token kind. */ int yychar; @@ -1245,55 +1214,47 @@ static YYLTYPE yyloc_default YYLTYPE yylloc = yyloc_default; /* Number of syntax errors so far. */ - int yynerrs; + int yynerrs = 0; - int yystate; + yy_state_fast_t yystate = 0; /* Number of tokens to shift before error messages enabled. */ - int yyerrstatus; + int yyerrstatus = 0; - /* The stacks and their tools: - 'yyss': related to states. - 'yyvs': related to semantic values. - 'yyls': related to locations. - - Refer to the stacks through separate pointers, to allow yyoverflow + /* Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ - /* The state stack. */ - yytype_int16 yyssa[YYINITDEPTH]; - yytype_int16 *yyss; - yytype_int16 *yyssp; + /* Their size. */ + YYPTRDIFF_T yystacksize = YYINITDEPTH; + + /* The state stack: array, bottom, top. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss = yyssa; + yy_state_t *yyssp = yyss; - /* The semantic value stack. */ + /* The semantic value stack: array, bottom, top. */ YYSTYPE yyvsa[YYINITDEPTH]; - YYSTYPE *yyvs; - YYSTYPE *yyvsp; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp = yyvs; - /* The location stack. */ + /* The location stack: array, bottom, top. */ YYLTYPE yylsa[YYINITDEPTH]; - YYLTYPE *yyls; - YYLTYPE *yylsp; - - /* The locations where the error started and ended. */ - YYLTYPE yyerror_range[3]; - - YYSIZE_T yystacksize; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp = yyls; int yyn; + /* The return value of yyparse. */ int yyresult; - /* Lookahead token as an internal (translated) token number. */ - int yytoken = 0; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; YYLTYPE yyloc; -#if YYERROR_VERBOSE - /* Buffer for error messages, and its allocated size. */ - char yymsgbuf[128]; - char *yymsg = yymsgbuf; - YYSIZE_T yymsg_alloc = sizeof yymsgbuf; -#endif + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[3]; + + #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) @@ -1301,43 +1262,49 @@ YYLTYPE yylloc = yyloc_default; Keep to zero when no symbol should be popped. */ int yylen = 0; - yyssp = yyss = yyssa; - yyvsp = yyvs = yyvsa; - yylsp = yyls = yylsa; - yystacksize = YYINITDEPTH; - YYDPRINTF ((stderr, "Starting parse\n")); - yystate = 0; - yyerrstatus = 0; - yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ + yylsp[0] = yylloc; goto yysetstate; + /*------------------------------------------------------------. -| yynewstate -- Push a new state, which is found in yystate. | +| yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ - yynewstate: +yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; - yysetstate: - *yyssp = yystate; + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + YYNOMEM; +#else { /* Get the current used size of the three stacks, in elements. */ - YYSIZE_T yysize = yyssp - yyss + 1; + YYPTRDIFF_T yysize = yyssp - yyss + 1; -#ifdef yyoverflow +# if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ + yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; - yytype_int16 *yyss1 = yyss; YYLTYPE *yyls1 = yyls; /* Each stack pointer address is followed by the size of the @@ -1345,32 +1312,29 @@ YYLTYPE yylloc = yyloc_default; conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), - &yyss1, yysize * sizeof (*yyssp), - &yyvs1, yysize * sizeof (*yyvsp), - &yyls1, yysize * sizeof (*yylsp), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yyls1, yysize * YYSIZEOF (*yylsp), &yystacksize); - - yyls = yyls1; yyss = yyss1; yyvs = yyvs1; + yyls = yyls1; } -#else /* no yyoverflow */ -# ifndef YYSTACK_RELOCATE - goto yyexhaustedlab; -# else +# else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) - goto yyexhaustedlab; + YYNOMEM; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { - yytype_int16 *yyss1 = yyss; + yy_state_t *yyss1 = yyss; union yyalloc *yyptr = - (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) - goto yyexhaustedlab; + YYNOMEM; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); YYSTACK_RELOCATE (yyls_alloc, yyls); @@ -1379,31 +1343,32 @@ YYLTYPE yylloc = yyloc_default; YYSTACK_FREE (yyss1); } # endif -#endif /* no yyoverflow */ yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; yylsp = yyls + yysize - 1; - YYDPRINTF ((stderr, "Stack size increased to %lu\n", - (unsigned long) yystacksize)); + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ - YYDPRINTF ((stderr, "Entering state %d\n", yystate)); if (yystate == YYFINAL) YYACCEPT; goto yybackup; + /*-----------. | yybackup. | `-----------*/ yybackup: - /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ @@ -1414,18 +1379,30 @@ YYLTYPE yylloc = yyloc_default; /* Not known => get a lookahead token if don't already have one. */ - /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ if (yychar == YYEMPTY) { - YYDPRINTF ((stderr, "Reading a token: ")); + YYDPRINTF ((stderr, "Reading a token\n")); yychar = yylex (&yylval, &yylloc, info); } if (yychar <= YYEOF) { - yychar = yytoken = YYEOF; + yychar = YYEOF; + yytoken = YYSYMBOL_YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } + else if (yychar == YYerror) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + yyerror_range[1] = yylloc; + goto yyerrlab1; + } else { yytoken = YYTRANSLATE (yychar); @@ -1453,15 +1430,14 @@ YYLTYPE yylloc = yyloc_default; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); - - /* Discard the shifted token. */ - yychar = YYEMPTY; - yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END *++yylsp = yylloc; + + /* Discard the shifted token. */ + yychar = YYEMPTY; goto yynewstate; @@ -1476,7 +1452,7 @@ YYLTYPE yylloc = yyloc_default; /*-----------------------------. -| yyreduce -- Do a reduction. | +| yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ @@ -1498,352 +1474,279 @@ YYLTYPE yylloc = yyloc_default; YY_REDUCE_PRINT (yyn); switch (yyn) { - case 5: - - { - yyHaveTime++; + case 4: /* item: time */ + { + yyIncrFlags(CLF_TIME); } - break; - case 6: - - { - yyHaveZone++; + case 5: /* item: zone */ + { + yyIncrFlags(CLF_ZONE); } - break; - case 7: - - { - yyHaveDate++; + case 6: /* item: date */ + { + yyIncrFlags(CLF_HAVEDATE); } - break; - case 8: - - { - yyHaveOrdinalMonth++; + case 7: /* item: ordMonth */ + { + yyIncrFlags(CLF_ORDINALMONTH); } - break; - case 9: - - { - yyHaveDay++; + case 8: /* item: day */ + { + yyIncrFlags(CLF_DAYOFWEEK); } - break; - case 10: - - { - yyHaveRel++; + case 9: /* item: relspec */ + { + info->flags |= CLF_RELCONV; } - break; - case 11: - - { - yyHaveTime++; - yyHaveDate++; + case 10: /* item: iso */ + { + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); } - break; - case 12: - - { - yyHaveTime++; - yyHaveDate++; - yyHaveRel++; + case 11: /* item: trek */ + { + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); + info->flags |= CLF_RELCONV; } - break; - case 14: + case 13: /* iextime: tUNUMBER ':' tUNUMBER ':' tUNUMBER */ + { + yyHour = (yyvsp[-4].Number); + yyMinutes = (yyvsp[-2].Number); + yySeconds = (yyvsp[0].Number); + } + break; - { - yyHour = (yyvsp[-1].Number); - yyMinutes = 0; + case 14: /* iextime: tUNUMBER ':' tUNUMBER */ + { + yyHour = (yyvsp[-2].Number); + yyMinutes = (yyvsp[0].Number); yySeconds = 0; - yyMeridian = (yyvsp[0].Meridian); } - break; - case 15: - - { - yyHour = (yyvsp[-3].Number); - yyMinutes = (yyvsp[-1].Number); + case 15: /* time: tUNUMBER tMERIDIAN */ + { + yyHour = (yyvsp[-1].Number); + yyMinutes = 0; yySeconds = 0; yyMeridian = (yyvsp[0].Meridian); } - break; - case 16: - - { - yyHour = (yyvsp[-5].Number); - yyMinutes = (yyvsp[-3].Number); - yySeconds = (yyvsp[-1].Number); + case 16: /* time: iextime o_merid */ + { yyMeridian = (yyvsp[0].Meridian); } - break; - case 17: - - { + case 17: /* zone: tZONE tDST */ + { yyTimezone = (yyvsp[-1].Number); yyDSTmode = DSTon; } - break; - case 18: - - { + case 18: /* zone: tZONE */ + { yyTimezone = (yyvsp[0].Number); yyDSTmode = DSToff; } - break; - case 19: - - { + case 19: /* zone: tDAYZONE */ + { yyTimezone = (yyvsp[0].Number); yyDSTmode = DSTon; } - break; - case 20: - - { /* GMT+0100, GMT-1000, etc. */ + case 20: /* zone: tZONEwO4 sign INTNUM */ + { /* GMT+0100, GMT-1000, etc. */ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60); yyDSTmode = DSToff; } - break; - case 21: - - { /* GMT+1, GMT-10, etc. */ + case 21: /* zone: tZONEwO2 sign INTNUM */ + { /* GMT+1, GMT-10, etc. */ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) * 60); yyDSTmode = DSToff; } - break; - case 22: - - { /* +0100, -0100 */ + case 22: /* zone: sign INTNUM */ + { /* +0100, -0100 */ yyTimezone = -(yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60); yyDSTmode = DSToff; } - break; - case 25: - - { + case 25: /* day: tDAY */ + { yyDayOrdinal = 1; yyDayOfWeek = (yyvsp[0].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 26: - - { + case 26: /* day: tDAY comma */ + { yyDayOrdinal = 1; yyDayOfWeek = (yyvsp[-1].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 27: - - { + case 27: /* day: tUNUMBER tDAY */ + { yyDayOrdinal = (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 28: - - { + case 28: /* day: sign SP tUNUMBER tDAY */ + { yyDayOrdinal = (yyvsp[-3].Number) * (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 29: - - { + case 29: /* day: sign tUNUMBER tDAY */ + { yyDayOrdinal = (yyvsp[-2].Number) * (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 30: - - { + case 30: /* day: tNEXT tDAY */ + { yyDayOrdinal = 2; yyDayOfWeek = (yyvsp[0].Number); - info->flags |= CLF_DAYOFWEEK; } - break; - case 31: - - { + case 31: /* iexdate: tUNUMBER '-' tUNUMBER '-' tUNUMBER */ + { yyMonth = (yyvsp[-2].Number); yyDay = (yyvsp[0].Number); + yyYear = (yyvsp[-4].Number); } - break; - case 32: + case 32: /* date: tUNUMBER '/' tUNUMBER */ + { + yyMonth = (yyvsp[-2].Number); + yyDay = (yyvsp[0].Number); + } + break; - { + case 33: /* date: tUNUMBER '/' tUNUMBER '/' tUNUMBER */ + { yyMonth = (yyvsp[-4].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - - break; - - case 33: - - { - yyYear = (yyvsp[0].Number) / 10000; - yyMonth = ((yyvsp[0].Number) % 10000)/100; - yyDay = (yyvsp[0].Number) % 100; - } - break; - case 34: - - { + case 35: /* date: tUNUMBER '-' tMONTH '-' tUNUMBER */ + { yyDay = (yyvsp[-4].Number); yyMonth = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - break; - case 35: - - { - yyMonth = (yyvsp[-2].Number); - yyDay = (yyvsp[0].Number); - yyYear = (yyvsp[-4].Number); - } - - break; - - case 36: - - { + case 36: /* date: tMONTH tUNUMBER */ + { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[0].Number); } - break; - case 37: - - { + case 37: /* date: tMONTH tUNUMBER comma tUNUMBER */ + { yyMonth = (yyvsp[-3].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - break; - case 38: - - { + case 38: /* date: tUNUMBER tMONTH */ + { yyMonth = (yyvsp[0].Number); yyDay = (yyvsp[-1].Number); } - break; - case 39: - - { + case 39: /* date: tEPOCH */ + { yyMonth = 1; yyDay = 1; yyYear = EPOCH; } - break; - case 40: - - { + case 40: /* date: tUNUMBER tMONTH tUNUMBER */ + { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } - break; - case 41: - - { + case 41: /* ordMonth: tNEXT tMONTH */ + { yyMonthOrdinalIncr = 1; yyMonthOrdinal = (yyvsp[0].Number); } - break; - case 42: - - { + case 42: /* ordMonth: tNEXT tUNUMBER tMONTH */ + { yyMonthOrdinalIncr = (yyvsp[-1].Number); yyMonthOrdinal = (yyvsp[0].Number); } + break; + case 45: /* isodate: tISOBAS8 */ + { /* YYYYMMDD */ + yyYear = (yyvsp[0].Number) / 10000; + yyMonth = ((yyvsp[0].Number) % 10000)/100; + yyDay = (yyvsp[0].Number) % 100; + } break; - case 43: + case 46: /* isodate: tISOBAS6 */ + { /* YYMMDD */ + yyYear = (yyvsp[0].Number) / 10000; + yyMonth = ((yyvsp[0].Number) % 10000)/100; + yyDay = (yyvsp[0].Number) % 100; + } + break; - { - if ((yyvsp[-1].Number) != HOUR( 7)) YYABORT; /* T */ - yyYear = (yyvsp[-2].Number) / 10000; - yyMonth = ((yyvsp[-2].Number) % 10000)/100; - yyDay = (yyvsp[-2].Number) % 100; + case 48: /* isotime: tISOBAS6 */ + { yyHour = (yyvsp[0].Number) / 10000; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } - break; - case 44: - - { + case 51: /* iso: tISOBASL tISOBAS6 */ + { /* YYYYMMDDhhmmss */ yyYear = (yyvsp[-1].Number) / 10000; yyMonth = ((yyvsp[-1].Number) % 10000)/100; yyDay = (yyvsp[-1].Number) % 100; @@ -1851,52 +1754,22 @@ YYLTYPE yylloc = yyloc_default; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } - - break; - - case 45: - - { - yyYear = (yyvsp[-6].Number) / 10000; - yyMonth = ((yyvsp[-6].Number) % 10000)/100; - yyDay = (yyvsp[-6].Number) % 100; - yyHour = (yyvsp[-4].Number); - yyMinutes = (yyvsp[-2].Number); - yySeconds = (yyvsp[0].Number); - } - break; - case 46: - - { - if ((yyvsp[-5].Number) != HOUR( 7)) YYABORT; /* T */ - yyYear = (yyvsp[-6].Number) / 10000; - yyMonth = ((yyvsp[-6].Number) % 10000)/100; - yyDay = (yyvsp[-6].Number) % 100; - yyHour = (yyvsp[-4].Number); - yyMinutes = (yyvsp[-2].Number); - yySeconds = (yyvsp[0].Number); - } - - break; - - case 47: - - { - yyYear = (yyvsp[-2].Number) / 10000; - yyMonth = ((yyvsp[-2].Number) % 10000)/100; - yyDay = (yyvsp[-2].Number) % 100; - yyHour = (yyvsp[0].Number) / 10000; - yyMinutes = ((yyvsp[0].Number) % 10000)/100; - yySeconds = (yyvsp[0].Number) % 100; + case 52: /* iso: tISOBASL tUNUMBER */ + { /* YYYYMMDDhhmm */ + if (yyDigitCount != 4) YYABORT; /* normally unreached */ + yyYear = (yyvsp[-1].Number) / 10000; + yyMonth = ((yyvsp[-1].Number) % 10000)/100; + yyDay = (yyvsp[-1].Number) % 100; + yyHour = (yyvsp[0].Number) / 100; + yyMinutes = ((yyvsp[0].Number) % 100); + yySeconds = 0; } - break; - case 48: - - { + case 53: /* trek: tSTARDATE INTNUM '.' tUNUMBER */ + { /* * Offset computed year by -377 so that the returned years will be * in a range accessible with a 32 bit clock seconds value. @@ -1908,133 +1781,109 @@ YYLTYPE yylloc = yyloc_default; yyRelDay += (((yyvsp[-2].Number)%1000)*(365 + IsLeapYear(yyYear)))/1000; yyRelSeconds += (yyvsp[0].Number) * 144 * 60; } - break; - case 49: - - { + case 54: /* relspec: relunits tAGO */ + { yyRelSeconds *= -1; yyRelMonth *= -1; yyRelDay *= -1; } - break; - case 51: - - { + case 56: /* relunits: sign SP INTNUM unit */ + { *yyRelPointer += (yyvsp[-3].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); } - break; - case 52: - - { + case 57: /* relunits: sign INTNUM unit */ + { *yyRelPointer += (yyvsp[-2].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); } - break; - case 53: - - { + case 58: /* relunits: INTNUM unit */ + { *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); } - break; - case 54: - - { + case 59: /* relunits: tNEXT unit */ + { *yyRelPointer += (yyvsp[0].Number); } - break; - case 55: - - { + case 60: /* relunits: tNEXT INTNUM unit */ + { *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); } - break; - case 56: - - { + case 61: /* relunits: unit */ + { *yyRelPointer += (yyvsp[0].Number); } - break; - case 57: - - { + case 62: /* sign: '-' */ + { (yyval.Number) = -1; } - break; - case 58: - - { + case 63: /* sign: '+' */ + { (yyval.Number) = 1; } - break; - case 59: - - { + case 64: /* unit: tSEC_UNIT */ + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelSeconds; } - break; - case 60: - - { + case 65: /* unit: tDAY_UNIT */ + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelDay; } - break; - case 61: - - { + case 66: /* unit: tMONTH_UNIT */ + { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelMonth; } - break; - case 62: - - { + case 67: /* INTNUM: tUNUMBER */ + { (yyval.Number) = (yyvsp[0].Number); } - break; - case 63: - - { + case 68: /* INTNUM: tISOBAS6 */ + { (yyval.Number) = (yyvsp[0].Number); } - break; - case 64: + case 69: /* INTNUM: tISOBAS8 */ + { + (yyval.Number) = (yyvsp[0].Number); + } + break; - { - if (yyHaveTime && yyHaveDate && !yyHaveRel) { + case 70: /* numitem: tUNUMBER */ + { + if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) { yyYear = (yyvsp[0].Number); } else { - yyHaveTime++; + yyIncrFlags(CLF_TIME); if (yyDigitCount <= 2) { yyHour = (yyvsp[0].Number); yyMinutes = 0; @@ -2046,23 +1895,18 @@ YYLTYPE yylloc = yyloc_default; yyMeridian = MER24; } } - break; - case 65: - - { + case 71: /* o_merid: %empty */ + { (yyval.Meridian) = MER24; } - break; - case 66: - - { + case 72: /* o_merid: tMERIDIAN */ + { (yyval.Meridian) = (yyvsp[0].Meridian); } - break; @@ -2080,11 +1924,10 @@ YYLTYPE yylloc = yyloc_default; case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ - YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; - YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; *++yylsp = yyloc; @@ -2092,14 +1935,13 @@ YYLTYPE yylloc = yyloc_default; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ - - yyn = yyr1[yyn]; - - yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; - if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) - yystate = yytable[yystate]; - else - yystate = yydefgoto[yyn - YYNTOKENS]; + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } goto yynewstate; @@ -2110,50 +1952,15 @@ YYLTYPE yylloc = yyloc_default; yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ - yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); - + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; -#if ! YYERROR_VERBOSE yyerror (&yylloc, info, YY_("syntax error")); -#else -# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ - yyssp, yytoken) - { - char const *yymsgp = YY_("syntax error"); - int yysyntax_error_status; - yysyntax_error_status = YYSYNTAX_ERROR; - if (yysyntax_error_status == 0) - yymsgp = yymsg; - else if (yysyntax_error_status == 1) - { - if (yymsg != yymsgbuf) - YYSTACK_FREE (yymsg); - yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); - if (!yymsg) - { - yymsg = yymsgbuf; - yymsg_alloc = sizeof yymsgbuf; - yysyntax_error_status = 2; - } - else - { - yysyntax_error_status = YYSYNTAX_ERROR; - yymsgp = yymsg; - } - } - yyerror (&yylloc, info, yymsgp); - if (yysyntax_error_status == 2) - goto yyexhaustedlab; - } -# undef YYSYNTAX_ERROR -#endif } yyerror_range[1] = yylloc; - if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an @@ -2182,12 +1989,11 @@ YYLTYPE yylloc = yyloc_default; | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: - - /* Pacify compilers like GCC when the user code never invokes - YYERROR and the label yyerrorlab therefore never appears in user - code. */ - if (/*CONSTCOND*/ 0) - goto yyerrorlab; + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + ++yynerrs; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ @@ -2204,13 +2010,14 @@ YYLTYPE yylloc = yyloc_default; yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ + /* Pop stack until we find a state that shifts the error token. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { - yyn += YYTERROR; - if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) { yyn = yytable[yyn]; if (0 < yyn) @@ -2224,7 +2031,7 @@ YYLTYPE yylloc = yyloc_default; yyerror_range[1] = *yylsp; yydestruct ("Error: popping", - yystos[yystate], yyvsp, yylsp, info); + YY_ACCESSING_SYMBOL (yystate), yyvsp, yylsp, info); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); @@ -2235,13 +2042,11 @@ YYLTYPE yylloc = yyloc_default; YY_IGNORE_MAYBE_UNINITIALIZED_END yyerror_range[2] = yylloc; - /* Using YYLLOC is tempting, but would change the location of - the lookahead. YYLOC is available though. */ - YYLLOC_DEFAULT (yyloc, yyerror_range, 2); - *++yylsp = yyloc; + ++yylsp; + YYLLOC_DEFAULT (*yylsp, yyerror_range, 2); /* Shift the error token. */ - YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); yystate = yyn; goto yynewstate; @@ -2252,26 +2057,30 @@ YYLTYPE yylloc = yyloc_default; `-------------------------------------*/ yyacceptlab: yyresult = 0; - goto yyreturn; + goto yyreturnlab; + /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; - goto yyreturn; + goto yyreturnlab; + -#if !defined yyoverflow || YYERROR_VERBOSE -/*-------------------------------------------------. -| yyexhaustedlab -- memory exhaustion comes here. | -`-------------------------------------------------*/ +/*-----------------------------------------------------------. +| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | +`-----------------------------------------------------------*/ yyexhaustedlab: yyerror (&yylloc, info, YY_("memory exhausted")); yyresult = 2; - /* Fall through. */ -#endif + goto yyreturnlab; -yyreturn: + +/*----------------------------------------------------------. +| yyreturnlab -- parsing is finished, clean up and return. | +`----------------------------------------------------------*/ +yyreturnlab: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at @@ -2287,17 +2096,14 @@ YYLTYPE yylloc = yyloc_default; while (yyssp != yyss) { yydestruct ("Cleanup: popping", - yystos[*yyssp], yyvsp, yylsp, info); + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, yylsp, info); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif -#if YYERROR_VERBOSE - if (yymsg != yymsgbuf) - YYSTACK_FREE (yymsg); -#endif + return yyresult; } @@ -2320,7 +2126,7 @@ static const TABLE MonthDayTable[] = { { "october", tMONTH, 10 }, { "november", tMONTH, 11 }, { "december", tMONTH, 12 }, - { "sunday", tDAY, 0 }, + { "sunday", tDAY, 7 }, { "monday", tDAY, 1 }, { "tuesday", tDAY, 2 }, { "tues", tDAY, 2 }, @@ -2507,7 +2313,7 @@ static const TABLE MilitaryTable[] = { static inline const char * bypassSpaces( - register const char *s) + const char *s) { if (isspace(UCHAR(*s))) { do { @@ -2528,6 +2334,9 @@ TclDateerror( const char *s) { Tcl_Obj* t; + if (!infoPtr->messages) { + infoPtr->messages = Tcl_NewObj(); + } Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1); Tcl_AppendToObj(infoPtr->messages, s, -1); Tcl_AppendToObj(infoPtr->messages, " (characters ", -1); @@ -2551,25 +2360,13 @@ ToSeconds( int Seconds, MERIDIAN Meridian) { - if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) { - return -1; - } switch (Meridian) { case MER24: - if (Hours < 0 || Hours > 23) { - return -1; - } - return (Hours * 60L + Minutes) * 60L + Seconds; + return (Hours * 60 + Minutes) * 60 + Seconds; case MERam: - if (Hours < 1 || Hours > 12) { - return -1; - } - return ((Hours % 12) * 60L + Minutes) * 60L + Seconds; + return ((Hours % 12) * 60 + Minutes) * 60 + Seconds; case MERpm: - if (Hours < 1 || Hours > 12) { - return -1; - } - return (((Hours % 12) + 12) * 60L + Minutes) * 60L + Seconds; + return (((Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds; } return -1; /* Should never be reached */ } @@ -2579,9 +2376,9 @@ LookupWord( YYSTYPE* yylvalPtr, char *buff) { - register char *p; - register char *q; - register const TABLE *tp; + char *p; + char *q; + const TABLE *tp; int i, abbrev; /* @@ -2704,10 +2501,11 @@ TclDatelex( YYLTYPE* location, DateInfo *info) { - register char c; - register char *p; + char c; + char *p; char buff[20]; int Count; + const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { @@ -2720,41 +2518,60 @@ TclDatelex( return SP; } } + tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ - /* - * Convert the string into a number; count the number of digits. + /* + * Count the number of digits. */ - register int num = c - '0'; p = (char *)yyInput; - while (isdigit(UCHAR(c = *(++p)))) { - num *= 10; - num += c - '0'; - }; - yylvalPtr->Number = num; + while (isdigit(UCHAR(*++p))) {}; yyDigitCount = p - yyInput; + /* + * A number with 12 or 14 digits is considered an ISO 8601 date. + */ + if (yyDigitCount == 14 || yyDigitCount == 12) { + /* long form of ISO 8601 (without separator), either + * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date + * (8 chars is isodate) */ + p = (char *)yyInput+8; + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; + } + /* + * Convert the string into a number + */ + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } yyInput = p; - - /* ignore spaces after digits (optional) */ - yyInput = bypassSpaces(yyInput); /* * A number with 6 or more digits is considered an ISO 8601 base. */ - + location->last_column = yyInput - info->dateStart - 1; if (yyDigitCount >= 6) { - location->last_column = yyInput - info->dateStart - 1; - return tISOBASE; - } else { - location->last_column = yyInput - info->dateStart - 1; - return tUNUMBER; + if (yyDigitCount == 8) { + return tISOBAS8; + } + if (yyDigitCount == 6) { + return tISOBAS6; + } } + /* ignore spaces after digits (optional) */ + yyInput = bypassSpaces(yyInput); + return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { - if (p < &buff[sizeof buff - 1]) { + if (p < &buff[sizeof(buff) - 1]) { *p++ = c; } } @@ -2763,12 +2580,19 @@ TclDatelex( location->last_column = yyInput - info->dateStart - 1; ret = LookupWord(yylvalPtr, buff); /* - * lookahead for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day", + * lookahead: + * for spaces to consider word boundaries (for instance + * literal T in isodateTisotimeZ is not a TZ, but Z is UTC); + * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day"; * bypass spaces after token (but ignore by TZ+OFFS), because should * recognize next SP token, if TZ only. */ if (ret == tZONE || ret == tDAYZONE) { c = *yyInput; + if (isdigit(UCHAR(c))) { /* literal not a TZ */ + yyInput = tokStart; + return *yyInput++; + } if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) { if ( !isdigit(UCHAR(*(yyInput+2))) || !isdigit(UCHAR(*(yyInput+3)))) { @@ -2825,9 +2649,7 @@ TclClockFreeScan( yyDSTmode = DSTmaybe; - info->messages = Tcl_NewObj(); info->separatrix = ""; - Tcl_IncrRefCount(info->messages); info->dateStart = yyInput; @@ -2837,58 +2659,44 @@ TclClockFreeScan( /* parse */ status = yyparse(info); if (status == 1) { - Tcl_SetObjResult(interp, info->messages); - Tcl_DecrRefCount(info->messages); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); - return TCL_ERROR; + const char *msg = NULL; + if (info->errFlags & CLF_HAVEDATE) { + msg = "more than one date in string"; + } else if (info->errFlags & CLF_TIME) { + msg = "more than one time of day in string"; + } else if (info->errFlags & CLF_ZONE) { + msg = "more than one time zone in string"; + } else if (info->errFlags & CLF_DAYOFWEEK) { + msg = "more than one weekday in string"; + } else if (info->errFlags & CLF_ORDINALMONTH) { + msg = "more than one ordinal month in string"; + } + if (msg) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); + } else { + Tcl_SetObjResult(interp, + info->messages ? info->messages : Tcl_NewObj()); + info->messages = NULL; + Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); + } + status = TCL_ERROR; } else if (status == 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1)); - Tcl_DecrRefCount(info->messages); Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL); - return TCL_ERROR; + status = TCL_ERROR; } else if (status != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned " "from date parser. Please " "report this error as a " "bug in Tcl.", -1)); - Tcl_DecrRefCount(info->messages); Tcl_SetErrorCode(interp, "TCL", "BUG", NULL); - return TCL_ERROR; + status = TCL_ERROR; } - Tcl_DecrRefCount(info->messages); - - if (yyHaveDate > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one date in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveTime > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one time of day in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveZone > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one time zone in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveDay > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one weekday in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveOrdinalMonth > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one ordinal month in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; + if (info->messages) { + Tcl_DecrRefCount(info->messages); } - - return TCL_OK; + return status; } /* diff --git a/generic/tclDate.h b/generic/tclDate.h index 2a59c5b..aed67a5 100644 --- a/generic/tclDate.h +++ b/generic/tclDate.h @@ -76,6 +76,7 @@ MODULE_SCOPE size_t TclEnvEpoch; /* Epoch of the tcl environment #define CLF_LOCALSEC (1 << 2) #define CLF_JULIANDAY (1 << 3) #define CLF_TIME (1 << 4) +#define CLF_ZONE (1 << 5) #define CLF_CENTURY (1 << 6) #define CLF_DAYOFMONTH (1 << 7) #define CLF_DAYOFYEAR (1 << 8) @@ -85,12 +86,19 @@ MODULE_SCOPE size_t TclEnvEpoch; /* Epoch of the tcl environment #define CLF_ISO8601YEAR (1 << 12) #define CLF_ISO8601WEAK (1 << 13) #define CLF_ISO8601CENTURY (1 << 14) + #define CLF_SIGNED (1 << 15) + +/* extra flags used outside of scan/format-tokens too (int, not a short int) */ +#define CLF_RELCONV (1 << 17) +#define CLF_ORDINALMONTH (1 << 18) + /* On demand (lazy) assemble flags */ #define CLF_ASSEMBLE_DATE (1 << 28) /* assemble year, month, etc. using julianDay */ #define CLF_ASSEMBLE_JULIANDAY (1 << 29) /* assemble julianDay using year, month, etc. */ #define CLF_ASSEMBLE_SECONDS (1 << 30) /* assemble localSeconds (and seconds at end) */ +#define CLF_HAVEDATE (CLF_DAYOFMONTH|CLF_MONTH|CLF_YEAR) #define CLF_DATE (CLF_JULIANDAY | CLF_DAYOFMONTH | CLF_DAYOFYEAR | \ CLF_MONTH | CLF_YEAR | CLF_ISO8601YEAR | \ CLF_DAYOFWEEK | CLF_ISO8601WEAK) @@ -181,6 +189,8 @@ typedef enum ClockMsgCtLiteral { * Structure containing the fields used in [clock format] and [clock scan] */ +#define CLF_CTZ (1 << 4) + typedef struct TclDateFields { /* Cacheable fields: */ @@ -203,8 +213,10 @@ typedef struct TclDateFields { int dayOfWeek; /* Day of the week */ int hour; /* Hours of day (in-between time only calculation) */ int minutes; /* Minutes of hour (in-between time only calculation) */ - int secondOfMin; /* Seconds of minute (in-between time only calculation) */ - int secondOfDay; /* Seconds of day (in-between time only calculation) */ + Tcl_WideInt secondOfMin; /* Seconds of minute (in-between time only calculation) */ + Tcl_WideInt secondOfDay; /* Seconds of day (in-between time only calculation) */ + + int flags; /* 0 or CLF_CTZ */ /* Non cacheable fields: */ @@ -226,30 +238,24 @@ typedef struct DateInfo { TclDateFields date; - int flags; - - int dateHaveDate; + int flags; /* Signals parts of date/time get found */ + int errFlags; /* Signals error (part of date/time found twice) */ int dateMeridian; - int dateHaveTime; int dateTimezone; int dateDSTmode; - int dateHaveZone; - int dateRelMonth; - int dateRelDay; - int dateRelSeconds; - int dateHaveRel; + Tcl_WideInt dateRelMonth; + Tcl_WideInt dateRelDay; + Tcl_WideInt dateRelSeconds; int dateMonthOrdinalIncr; int dateMonthOrdinal; - int dateHaveOrdinalMonth; int dateDayOrdinal; - int dateHaveDay; - int *dateRelPointer; + Tcl_WideInt *dateRelPointer; int dateSpaceCount; int dateDigitCount; @@ -276,12 +282,6 @@ typedef struct DateInfo { #define yyDayOfWeek (info->date.dayOfWeek) #define yyMonthOrdinalIncr (info->dateMonthOrdinalIncr) #define yyMonthOrdinal (info->dateMonthOrdinal) -#define yyHaveDate (info->dateHaveDate) -#define yyHaveDay (info->dateHaveDay) -#define yyHaveOrdinalMonth (info->dateHaveOrdinalMonth) -#define yyHaveRel (info->dateHaveRel) -#define yyHaveTime (info->dateHaveTime) -#define yyHaveZone (info->dateHaveZone) #define yyTimezone (info->dateTimezone) #define yyMeridian (info->dateMeridian) #define yyRelMonth (info->dateRelMonth) @@ -301,7 +301,9 @@ ClockInitDateInfo(DateInfo *info) { * Structure containing the command arguments supplied to [clock format] and [clock scan] */ -#define CLF_VALIDATE (1 << 2) +#define CLF_VALIDATE_S1 (1 << 0) +#define CLF_VALIDATE_S2 (1 << 1) +#define CLF_VALIDATE (CLF_VALIDATE_S1|CLF_VALIDATE_S2) #define CLF_EXTENDED (1 << 4) #define CLF_STRICT (1 << 8) #define CLF_LOCALE_USED (1 << 15) @@ -351,6 +353,7 @@ typedef struct ClockClientData { int yearOfCenturySwitch; int validMinYear; int validMaxYear; + double maxJDN; Tcl_Obj *systemTimeZone; Tcl_Obj *systemSetupTZData; @@ -516,6 +519,31 @@ struct ClockFmtScnStorage { #endif }; +/* + * Clock macros. + */ + +/* + * Extracts Julian day and seconds of the day from posix seconds (tm). + */ +#define ClockExtractJDAndSODFromSeconds(jd, sod, tm) \ + if (1) { \ + jd = (tm + JULIAN_SEC_POSIX_EPOCH); \ + if (jd >= SECONDS_PER_DAY || jd <= -SECONDS_PER_DAY) { \ + jd /= SECONDS_PER_DAY; \ + sod = (int)(tm % SECONDS_PER_DAY); \ + } else { \ + sod = (int)jd, jd = 0; \ + } \ + if (sod < 0) { \ + sod += SECONDS_PER_DAY; \ + /* JD is affected, if switched into negative (avoid 24 hours difference) */ \ + if (jd <= 0) { \ + jd--; \ + } \ + } \ + } + /* * Prototypes of module functions. */ @@ -557,6 +585,12 @@ MODULE_SCOPE int ClockMCSetIdx(ClockFmtScnCmdArgs *opts, int mcKey, /* tclClockFmt.c module declarations */ + +MODULE_SCOPE char * + TclItoAw(char *buf, int val, char padchar, unsigned short int width); +MODULE_SCOPE int + TclAtoWIe(Tcl_WideInt *out, const char *p, const char *e, int sign); + MODULE_SCOPE Tcl_Obj* ClockFrmObjGetLocFmtKey(Tcl_Interp *interp, Tcl_Obj *objPtr); @@ -574,5 +608,6 @@ MODULE_SCOPE int ClockFormat(register DateFormat *dateFmt, ClockFmtScnCmdArgs *opts); MODULE_SCOPE void ClockFrmScnClearCaches(void); +MODULE_SCOPE void ClockFrmScnFinalize(); #endif /* _TCLCLOCK_H */ diff --git a/generic/tclGetDate.y b/generic/tclGetDate.y index f25f45b..88fbe32 100644 --- a/generic/tclGetDate.y +++ b/generic/tclGetDate.y @@ -7,9 +7,9 @@ * only used when doing free-form date parsing, an ill-defined process * anyway. * - * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans. - * Copyright (c) 1995-1997 Sun Microsystems, Inc. - * Copyright (c) 2015 Sergey G. Brester aka sebres. + * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. + * Copyright © 1995-1997 Sun Microsystems, Inc. + * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -17,7 +17,7 @@ %parse-param {DateInfo* info} %lex-param {DateInfo* info} -%pure-parser +%define api.pure /* %error-verbose would be nice, but our token names are meaningless */ %locations @@ -28,8 +28,9 @@ * This file is generated from a yacc grammar defined in the file * tclGetDate.y. It should not be edited directly. * - * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans. - * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. + * Copyright © 1995-1997 Sun Microsystems, Inc. + * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -71,18 +72,24 @@ #define TM_YEAR_BASE 1900 -#define HOUR(x) ((int) (60 * x)) -#define SECSPERDAY (24L * 60L * 60L) -#define IsLeapYear(x) ((x % 4 == 0) && (x % 100 != 0 || x % 400 == 0)) +#define HOUR(x) ((60 * (int)(x))) +#define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0)) + +#define yyIncrFlags(f) \ + do { \ + info->errFlags |= (info->flags & (f)); \ + if (info->errFlags) { YYABORT; } \ + info->flags |= (f); \ + } while (0); /* * An entry in the lexical lookup table. */ -typedef struct _TABLE { +typedef struct { const char *name; int type; - time_t value; + int value; } TABLE; /* @@ -96,7 +103,7 @@ typedef enum _DSTMODE { %} %union { - time_t Number; + Tcl_WideInt Number; enum _MERIDIAN Meridian; } @@ -107,9 +114,9 @@ typedef enum _DSTMODE { */ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); - static void TclDateerror(YYLTYPE* location, +static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); - static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, +static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo* info); MODULE_SCOPE int yyparse(DateInfo*); @@ -130,7 +137,9 @@ MODULE_SCOPE int yyparse(DateInfo*); %token tZONEwO2 %token tEPOCH %token tDST -%token tISOBASE +%token tISOBAS8 +%token tISOBAS6 +%token tISOBASL %token tDAY_UNIT %token tNEXT %token SP @@ -146,7 +155,9 @@ MODULE_SCOPE int yyparse(DateInfo*); %type tZONE %type tZONEwO4 %type tZONEwO2 -%type tISOBASE +%type tISOBAS8 +%type tISOBAS6 +%type tISOBASL %type tDAY_UNIT %type unit %type sign @@ -159,56 +170,56 @@ MODULE_SCOPE int yyparse(DateInfo*); spec : /* NULL */ | spec item - | spec SP item + /* | spec SP item */ ; item : time { - yyHaveTime++; + yyIncrFlags(CLF_TIME); } | zone { - yyHaveZone++; + yyIncrFlags(CLF_ZONE); } | date { - yyHaveDate++; + yyIncrFlags(CLF_HAVEDATE); } | ordMonth { - yyHaveOrdinalMonth++; + yyIncrFlags(CLF_ORDINALMONTH); } | day { - yyHaveDay++; + yyIncrFlags(CLF_DAYOFWEEK); } | relspec { - yyHaveRel++; + info->flags |= CLF_RELCONV; } | iso { - yyHaveTime++; - yyHaveDate++; + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); } | trek { - yyHaveTime++; - yyHaveDate++; - yyHaveRel++; + yyIncrFlags(CLF_TIME|CLF_HAVEDATE); + info->flags |= CLF_RELCONV; } - | number + | numitem ; -time : tUNUMBER tMERIDIAN { +iextime : tUNUMBER ':' tUNUMBER ':' tUNUMBER { yyHour = $1; - yyMinutes = 0; - yySeconds = 0; - yyMeridian = $2; + yyMinutes = $3; + yySeconds = $5; } - | tUNUMBER ':' tUNUMBER o_merid { + | tUNUMBER ':' tUNUMBER { yyHour = $1; yyMinutes = $3; yySeconds = 0; - yyMeridian = $4; } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { + ; +time : tUNUMBER tMERIDIAN { yyHour = $1; - yyMinutes = $3; - yySeconds = $5; - yyMeridian = $6; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = $2; + } + | iextime o_merid { + yyMeridian = $2; } ; @@ -245,35 +256,35 @@ comma : ',' day : tDAY { yyDayOrdinal = 1; yyDayOfWeek = $1; - info->flags |= CLF_DAYOFWEEK; } | tDAY comma { yyDayOrdinal = 1; yyDayOfWeek = $1; - info->flags |= CLF_DAYOFWEEK; } | tUNUMBER tDAY { yyDayOrdinal = $1; yyDayOfWeek = $2; - info->flags |= CLF_DAYOFWEEK; } | sign SP tUNUMBER tDAY { yyDayOrdinal = $1 * $3; yyDayOfWeek = $4; - info->flags |= CLF_DAYOFWEEK; } | sign tUNUMBER tDAY { yyDayOrdinal = $1 * $2; yyDayOfWeek = $3; - info->flags |= CLF_DAYOFWEEK; } | tNEXT tDAY { yyDayOrdinal = 2; yyDayOfWeek = $2; - info->flags |= CLF_DAYOFWEEK; } ; +iexdate : tUNUMBER '-' tUNUMBER '-' tUNUMBER { + yyMonth = $3; + yyDay = $5; + yyYear = $1; + } + ; date : tUNUMBER '/' tUNUMBER { yyMonth = $1; yyDay = $3; @@ -283,21 +294,12 @@ date : tUNUMBER '/' tUNUMBER { yyDay = $3; yyYear = $5; } - | tISOBASE { - yyYear = $1 / 10000; - yyMonth = ($1 % 10000)/100; - yyDay = $1 % 100; - } + | isodate | tUNUMBER '-' tMONTH '-' tUNUMBER { yyDay = $1; yyMonth = $3; yyYear = $5; } - | tUNUMBER '-' tUNUMBER '-' tUNUMBER { - yyMonth = $3; - yyDay = $5; - yyYear = $1; - } | tMONTH tUNUMBER { yyMonth = $1; yyDay = $2; @@ -333,47 +335,44 @@ ordMonth: tNEXT tMONTH { } ; -iso : tISOBASE tZONE tISOBASE { - if ($2 != HOUR( 7)) YYABORT; /* T */ +isosep : 'T'|SP + ; +isodate : tISOBAS8 { /* YYYYMMDD */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; - yyHour = $3 / 10000; - yyMinutes = ($3 % 10000)/100; - yySeconds = $3 % 100; } - | tISOBASE tISOBASE { + | tISOBAS6 { /* YYMMDD */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; - yyHour = $2 / 10000; - yyMinutes = ($2 % 10000)/100; - yySeconds = $2 % 100; } - | tISOBASE SP tUNUMBER ':' tUNUMBER ':' tUNUMBER { - yyYear = $1 / 10000; - yyMonth = ($1 % 10000)/100; - yyDay = $1 % 100; - yyHour = $3; - yyMinutes = $5; - yySeconds = $7; + | iexdate + ; +isotime : tISOBAS6 { + yyHour = $1 / 10000; + yyMinutes = ($1 % 10000)/100; + yySeconds = $1 % 100; } - | tISOBASE tZONE tUNUMBER ':' tUNUMBER ':' tUNUMBER { - if ($2 != HOUR( 7)) YYABORT; /* T */ + | iextime + ; +iso : isodate isosep isotime + | tISOBASL tISOBAS6 { /* YYYYMMDDhhmmss */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; - yyHour = $3; - yyMinutes = $5; - yySeconds = $7; + yyHour = $2 / 10000; + yyMinutes = ($2 % 10000)/100; + yySeconds = $2 % 100; } - | tISOBASE SP tISOBASE { + | tISOBASL tUNUMBER { /* YYYYMMDDhhmm */ + if (yyDigitCount != 4) YYABORT; /* normally unreached */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; - yyHour = $3 / 10000; - yyMinutes = ($3 % 10000)/100; - yySeconds = $3 % 100; + yyHour = $2 / 100; + yyMinutes = ($2 % 100); + yySeconds = 0; } ; @@ -444,16 +443,19 @@ unit : tSEC_UNIT { INTNUM : tUNUMBER { $$ = $1; } - | tISOBASE { + | tISOBAS6 { + $$ = $1; + } + | tISOBAS8 { $$ = $1; } ; -number : INTNUM { - if (yyHaveTime && yyHaveDate && !yyHaveRel) { +numitem : tUNUMBER { + if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) { yyYear = $1; } else { - yyHaveTime++; + yyIncrFlags(CLF_TIME); if (yyDigitCount <= 2) { yyHour = $1; yyMinutes = 0; @@ -494,7 +496,7 @@ static const TABLE MonthDayTable[] = { { "october", tMONTH, 10 }, { "november", tMONTH, 11 }, { "december", tMONTH, 12 }, - { "sunday", tDAY, 0 }, + { "sunday", tDAY, 7 }, { "monday", tDAY, 1 }, { "tuesday", tDAY, 2 }, { "tues", tDAY, 2 }, @@ -681,7 +683,7 @@ static const TABLE MilitaryTable[] = { static inline const char * bypassSpaces( - register const char *s) + const char *s) { if (isspace(UCHAR(*s))) { do { @@ -702,6 +704,9 @@ TclDateerror( const char *s) { Tcl_Obj* t; + if (!infoPtr->messages) { + infoPtr->messages = Tcl_NewObj(); + } Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1); Tcl_AppendToObj(infoPtr->messages, s, -1); Tcl_AppendToObj(infoPtr->messages, " (characters ", -1); @@ -725,25 +730,13 @@ ToSeconds( int Seconds, MERIDIAN Meridian) { - if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) { - return -1; - } switch (Meridian) { case MER24: - if (Hours < 0 || Hours > 23) { - return -1; - } - return (Hours * 60L + Minutes) * 60L + Seconds; + return (Hours * 60 + Minutes) * 60 + Seconds; case MERam: - if (Hours < 1 || Hours > 12) { - return -1; - } - return ((Hours % 12) * 60L + Minutes) * 60L + Seconds; + return ((Hours % 12) * 60 + Minutes) * 60 + Seconds; case MERpm: - if (Hours < 1 || Hours > 12) { - return -1; - } - return (((Hours % 12) + 12) * 60L + Minutes) * 60L + Seconds; + return (((Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds; } return -1; /* Should never be reached */ } @@ -753,9 +746,9 @@ LookupWord( YYSTYPE* yylvalPtr, char *buff) { - register char *p; - register char *q; - register const TABLE *tp; + char *p; + char *q; + const TABLE *tp; int i, abbrev; /* @@ -878,10 +871,11 @@ TclDatelex( YYLTYPE* location, DateInfo *info) { - register char c; - register char *p; + char c; + char *p; char buff[20]; int Count; + const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { @@ -894,41 +888,60 @@ TclDatelex( return SP; } } + tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ - /* - * Convert the string into a number; count the number of digits. + /* + * Count the number of digits. */ - register int num = c - '0'; p = (char *)yyInput; - while (isdigit(UCHAR(c = *(++p)))) { - num *= 10; - num += c - '0'; - }; - yylvalPtr->Number = num; + while (isdigit(UCHAR(*++p))) {}; yyDigitCount = p - yyInput; + /* + * A number with 12 or 14 digits is considered an ISO 8601 date. + */ + if (yyDigitCount == 14 || yyDigitCount == 12) { + /* long form of ISO 8601 (without separator), either + * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date + * (8 chars is isodate) */ + p = (char *)yyInput+8; + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } + yyDigitCount = 8; + yyInput = p; + location->last_column = yyInput - info->dateStart - 1; + return tISOBASL; + } + /* + * Convert the string into a number + */ + if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { + return tID; /* overflow*/ + } yyInput = p; - - /* ignore spaces after digits (optional) */ - yyInput = bypassSpaces(yyInput); /* * A number with 6 or more digits is considered an ISO 8601 base. */ - + location->last_column = yyInput - info->dateStart - 1; if (yyDigitCount >= 6) { - location->last_column = yyInput - info->dateStart - 1; - return tISOBASE; - } else { - location->last_column = yyInput - info->dateStart - 1; - return tUNUMBER; + if (yyDigitCount == 8) { + return tISOBAS8; + } + if (yyDigitCount == 6) { + return tISOBAS6; + } } + /* ignore spaces after digits (optional) */ + yyInput = bypassSpaces(yyInput); + return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { - if (p < &buff[sizeof buff - 1]) { + if (p < &buff[sizeof(buff) - 1]) { *p++ = c; } } @@ -937,12 +950,19 @@ TclDatelex( location->last_column = yyInput - info->dateStart - 1; ret = LookupWord(yylvalPtr, buff); /* - * lookahead for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day", + * lookahead: + * for spaces to consider word boundaries (for instance + * literal T in isodateTisotimeZ is not a TZ, but Z is UTC); + * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day"; * bypass spaces after token (but ignore by TZ+OFFS), because should * recognize next SP token, if TZ only. */ if (ret == tZONE || ret == tDAYZONE) { c = *yyInput; + if (isdigit(UCHAR(c))) { /* literal not a TZ */ + yyInput = tokStart; + return *yyInput++; + } if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) { if ( !isdigit(UCHAR(*(yyInput+2))) || !isdigit(UCHAR(*(yyInput+3)))) { @@ -999,9 +1019,7 @@ TclClockFreeScan( yyDSTmode = DSTmaybe; - info->messages = Tcl_NewObj(); info->separatrix = ""; - Tcl_IncrRefCount(info->messages); info->dateStart = yyInput; @@ -1011,58 +1029,44 @@ TclClockFreeScan( /* parse */ status = yyparse(info); if (status == 1) { - Tcl_SetObjResult(interp, info->messages); - Tcl_DecrRefCount(info->messages); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); - return TCL_ERROR; + const char *msg = NULL; + if (info->errFlags & CLF_HAVEDATE) { + msg = "more than one date in string"; + } else if (info->errFlags & CLF_TIME) { + msg = "more than one time of day in string"; + } else if (info->errFlags & CLF_ZONE) { + msg = "more than one time zone in string"; + } else if (info->errFlags & CLF_DAYOFWEEK) { + msg = "more than one weekday in string"; + } else if (info->errFlags & CLF_ORDINALMONTH) { + msg = "more than one ordinal month in string"; + } + if (msg) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); + } else { + Tcl_SetObjResult(interp, + info->messages ? info->messages : Tcl_NewObj()); + info->messages = NULL; + Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", NULL); + } + status = TCL_ERROR; } else if (status == 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1)); - Tcl_DecrRefCount(info->messages); Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL); - return TCL_ERROR; + status = TCL_ERROR; } else if (status != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned " "from date parser. Please " "report this error as a " "bug in Tcl.", -1)); - Tcl_DecrRefCount(info->messages); Tcl_SetErrorCode(interp, "TCL", "BUG", NULL); - return TCL_ERROR; - } - Tcl_DecrRefCount(info->messages); - - if (yyHaveDate > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one date in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; + status = TCL_ERROR; } - if (yyHaveTime > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one time of day in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveZone > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one time zone in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveDay > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one weekday in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; - } - if (yyHaveOrdinalMonth > 1) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj("more than one ordinal month in string", -1)); - Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", NULL); - return TCL_ERROR; + if (info->messages) { + Tcl_DecrRefCount(info->messages); } - - return TCL_OK; + return status; } /* diff --git a/generic/tclStrIdxTree.h b/generic/tclStrIdxTree.h index 6dd3d3c..1ec3799 100644 --- a/generic/tclStrIdxTree.h +++ b/generic/tclStrIdxTree.h @@ -112,19 +112,6 @@ TclUtfFindEqualNCInLwr( return ret; } -static inline const char * -TclUtfNext( - register const char *src) /* The current location in the string. */ -{ - if (((unsigned char) *(src)) < 0xC0) { - return ++src; - } else { - Tcl_UniChar ch; - return src + TclUtfToUniChar(src, &ch); - } -} - - /* * Primitives to safe set, reset and free references. */ diff --git a/lib/clock.tcl.in b/lib/clock.tcl.in index 5fa321e..422a25e 100644 --- a/lib/clock.tcl.in +++ b/lib/clock.tcl.in @@ -9,8 +9,8 @@ # #---------------------------------------------------------------------- # -# Copyright (c) 2004,2005,2006,2007 by Kevin B. Kenny -# Copyright (c) 2015 by Sergey G. Brester aka sebres. +# Copyright © 2004-2007 Kevin B. Kenny +# Copyright © 2015 by Sergey G. Brester aka sebres. # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # @@ -214,7 +214,7 @@ proc ::tcl::clock::Initialize {} { ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 - # Romania (Transylvania changed earler - perhaps de_RO should show the + # Romania (Transylvania changed earlier - perhaps de_RO should show the # earlier date?) ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 @@ -472,8 +472,7 @@ proc ::tcl::clock::Initialize {} { # Caches - variable LocaleFormats \ - [dict create]; # Dictionary with localized formats + variable LocFmtMap [dict create]; # Dictionary with localized format maps variable TimeZoneBad [dict create]; # Dictionary whose keys are time zone # names and whose values are 1 if @@ -565,12 +564,18 @@ proc ::tcl::clock::mcMerge {locales} { set mrgcat [mcMerge [lrange $locales 1 end]] if {[dict exists $Msgs $ns $loc]} { set mrgcat [dict merge $mrgcat [dict get $Msgs $ns $loc]] + dict set mrgcat L $loc + } else { + # be sure a duplicate is created, don't overwrite {} (common) locale: + set mrgcat [dict merge $mrgcat [dict create L $loc]] } } else { if {[dict exists $Msgs $ns $loc]} { set mrgcat [dict get $Msgs $ns $loc] + dict set mrgcat L $loc } else { - set mrgcat [dict create] + # be sure a duplicate is created, don't overwrite {} (common) locale: + set mrgcat [dict create L $loc] } } dict set mcMergedCat $loc $mrgcat @@ -814,6 +819,8 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # locale -- Current [mclocale] locale, supplied to avoid # an extra call # format -- Format supplied to [clock scan] or [clock format] +# mcd -- Message catalog dictionary for current locale (read-only, +# don't store it to avoid shared references). # # Results: # Returns the string with locale-dependent composite format groups @@ -824,54 +831,41 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # #---------------------------------------------------------------------- -proc ::tcl::clock::LocalizeFormat { locale format {fmtkey {}} } { - variable LocaleFormats - - if { $fmtkey eq {} } { set fmtkey FMT_$format } - if { [catch { - set locfmt [dict get $LocaleFormats $locale $fmtkey] - }] } { - - # get map list cached or build it: - if { [catch { - set mlst [dict get $LocaleFormats $locale MLST] - }] } { - - # message catalog dictionary: - set mcd [mcget $locale] - - # Handle locale-dependent format groups by mapping them out of the format - # string. Note that the order of the [string map] operations is - # significant because later formats can refer to later ones; for example - # %c can refer to %X, which in turn can refer to %T. +proc ::tcl::clock::LocalizeFormat { locale format mcd } { + variable LocFmtMap - set mlst { - %% %% - %D %m/%d/%Y - %+ {%a %b %e %H:%M:%S %Z %Y} - } - lappend mlst %EY [string map $mlst [dict get $mcd LOCALE_YEAR_FORMAT]] - lappend mlst %T [string map $mlst [dict get $mcd TIME_FORMAT_24_SECS]] - lappend mlst %R [string map $mlst [dict get $mcd TIME_FORMAT_24]] - lappend mlst %r [string map $mlst [dict get $mcd TIME_FORMAT_12]] - lappend mlst %X [string map $mlst [dict get $mcd TIME_FORMAT]] - lappend mlst %EX [string map $mlst [dict get $mcd LOCALE_TIME_FORMAT]] - lappend mlst %x [string map $mlst [dict get $mcd DATE_FORMAT]] - lappend mlst %Ex [string map $mlst [dict get $mcd LOCALE_DATE_FORMAT]] - lappend mlst %c [string map $mlst [dict get $mcd DATE_TIME_FORMAT]] - lappend mlst %Ec [string map $mlst [dict get $mcd LOCALE_DATE_TIME_FORMAT]] - - dict set LocaleFormats $locale MLST $mlst + # get map list cached or build it: + if {[dict exists $LocFmtMap $locale]} { + set mlst [dict get $LocFmtMap $locale] + } else { + # Handle locale-dependent format groups by mapping them out of the format + # string. Note that the order of the [string map] operations is + # significant because later formats can refer to later ones; for example + # %c can refer to %X, which in turn can refer to %T. + + set mlst { + %% %% + %D %m/%d/%Y + %+ {%a %b %e %H:%M:%S %Z %Y} } + lappend mlst %EY [string map $mlst [dict get $mcd LOCALE_YEAR_FORMAT]] + lappend mlst %T [string map $mlst [dict get $mcd TIME_FORMAT_24_SECS]] + lappend mlst %R [string map $mlst [dict get $mcd TIME_FORMAT_24]] + lappend mlst %r [string map $mlst [dict get $mcd TIME_FORMAT_12]] + lappend mlst %X [string map $mlst [dict get $mcd TIME_FORMAT]] + lappend mlst %EX [string map $mlst [dict get $mcd LOCALE_TIME_FORMAT]] + lappend mlst %x [string map $mlst [dict get $mcd DATE_FORMAT]] + lappend mlst %Ex [string map $mlst [dict get $mcd LOCALE_DATE_FORMAT]] + lappend mlst %c [string map $mlst [dict get $mcd DATE_TIME_FORMAT]] + lappend mlst %Ec [string map $mlst [dict get $mcd LOCALE_DATE_TIME_FORMAT]] - # translate copy of format (don't use format object here, because otherwise - # it can lose its internal representation (string map - convert to unicode) - set locfmt [string map $mlst [string range " $format" 1 end]] - - # cache it: - dict set LocaleFormats $locale $fmtkey $locfmt + dict set LocFmtMap $locale $mlst } + # translate copy of format (don't use format object here, because otherwise + # it can lose its internal representation (string map - convert to unicode) + set locfmt [string map $mlst [string range " $format" 1 end]] + # Save original format as long as possible, because of internal # representation (performance). # Note that in this case such format will be never localized (also @@ -910,8 +904,7 @@ proc ::tcl::clock::GetSystemTimeZone {} { set timezone $result } elseif {[set result [getenv TZ]] ne {}} { set timezone $result - } - if {![info exists timezone]} { + } else { # ask engine for the cached timezone: set timezone [configure -system-tz] if { $timezone ne "" } { @@ -970,10 +963,7 @@ proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { "time zone \"$timezone\" not found" } variable MINWIDE - if { $timezone eq {:localtime} } { - # Nothing to do, we'll convert using the localtime function - - } elseif { + if { [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ -> s hh mm ss] } then { @@ -1004,24 +994,29 @@ proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { LoadTimeZoneFile [string range $timezone 1 end] }] && [catch { LoadZoneinfoFile [string range $timezone 1 end] - }] + } ret opts] } then { + dict unset opts -errorinfo + if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { + dict set opts -errorcode [list CLOCK badTimeZone $timezone] + set ret "time zone \"$timezone\" not found: $ret" + } dict set TimeZoneBad $timezone 1 - return -code error \ - -errorcode [list CLOCK badTimeZone $timezone] \ - "time zone \"$timezone\" not found" + return -options $opts $ret } } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { # This looks like a POSIX time zone - try to process it - if { [catch {ProcessPosixTimeZone $tzfields} data opts] } { - if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } { - dict unset opts -errorinfo + if { [catch {ProcessPosixTimeZone $tzfields} ret opts] } { + dict unset opts -errorinfo + if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { + dict set opts -errorcode [list CLOCK badTimeZone $timezone] + set ret "time zone \"$timezone\" not found: $ret" } dict set TimeZoneBad $timezone 1 - return -options $opts $data + return -options $opts $ret } else { - set TZData($timezone) $data + set TZData($timezone) $ret } } else { @@ -1032,7 +1027,7 @@ proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { # time zone file - this time without a colon if { [catch { LoadTimeZoneFile $timezone }] - && [catch { LoadZoneinfoFile $timezone } - opts] } { + && [catch { LoadZoneinfoFile $timezone } ret opts] } { # Check may be a legacy zone: @@ -1046,8 +1041,12 @@ proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { } dict unset opts -errorinfo + if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { + dict set opts -errorcode [list CLOCK badTimeZone $timezone] + set ret "time zone \"$timezone\" not found: $ret" + } dict set TimeZoneBad $timezone 1 - return -options $opts "time zone $timezone not found" + return -options $opts $ret } set TZData($timezone) $TZData(:$timezone) } @@ -1227,9 +1226,9 @@ proc ::tcl::clock::LoadTimeZoneFile { fileName } { # is security sensitive. Make sure that the path name cannot escape the # given directory. - if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + if { [regexp {^[/\\]|^[a-zA-Z]+:|(?:^|[/\\])\.\.} $fileName] } { return -code error \ - -errorcode [list CLOCK badTimeZone $:fileName] \ + -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not valid" } try { @@ -1267,17 +1266,23 @@ proc ::tcl::clock::LoadZoneinfoFile { fileName } { # is security sensitive. Make sure that the path name cannot escape the # given directory. - if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + if { [regexp {^[/\\]|^[a-zA-Z]+:|(?:^|[/\\])\.\.} $fileName] } { return -code error \ - -errorcode [list CLOCK badTimeZone $:fileName] \ + -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not valid" } + set fname "" foreach d $ZoneinfoPaths { set fname [file join $d $fileName] if { [file readable $fname] && [file isfile $fname] } { break } - unset fname + set fname "" + } + if {$fname eq ""} { + return -code error \ + -errorcode [list CLOCK badTimeZone :$fileName] \ + "time zone \":$fileName\" not found" } ReadZoneinfoFile $fileName $fname } @@ -1321,7 +1326,7 @@ proc ::tcl::clock::ReadZoneinfoFile {fileName fname} { close $f # The file begins with a magic number, sixteen reserved bytes, and then - # six 4-byte integers giving counts of fileds in the file. + # six 4-byte integers giving counts of fields in the file. binary scan $d a4a1x15IIIIII \ magic version nIsGMT nIsStd nLeap nTime nType nChar @@ -2050,7 +2055,6 @@ proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { #---------------------------------------------------------------------- proc ::tcl::clock::ChangeCurrentLocale {args} { - configure -current-locale [lindex $args 0] } @@ -2072,7 +2076,7 @@ proc ::tcl::clock::ChangeCurrentLocale {args} { #---------------------------------------------------------------------- proc ::tcl::clock::ClearCaches {} { - variable LocaleFormats + variable LocFmtMap variable mcMergedCat variable TimeZoneBad @@ -2082,7 +2086,7 @@ proc ::tcl::clock::ClearCaches {} { # clear msgcat cache: set mcMergedCat [dict create] - set LocaleFormats {} + set LocFmtMap {} set TimeZoneBad {} InitTZData } diff --git a/lib/loader.tcl b/lib/loader.tcl index 1352043..1e5fcf2 100644 --- a/lib/loader.tcl +++ b/lib/loader.tcl @@ -3,21 +3,24 @@ set ::tcl::clock::LibDir [file dirname [info script]] # rewrite clock ensemble: proc ::clock args { + if {$::tcl_platform(platform) ne {windows}} { + set lib "unix" + set name "lib" + } else { + set lib "win" + set name "" + } + append name tclclockmod * [info sharedlibextension] # first try from lib directory (like installed): - set lib [glob -nocomplain [file join $::tcl::clock::LibDir tclclockmod*[info sharedlibextension]]] + set lib [glob -nocomplain [file join $::tcl::clock::LibDir $name]] # second try find library from current directory (debug, release, platform etc.), # hereafter in path relative current lib (like uninstalled): if {![llength $lib]} { - foreach plib [list [pwd] [file dirname $::tcl::clock::LibDir]] { + foreach plib [list \ + [if {[info exists ::BUILDDIR]} {set ::BUILDDIR} else pwd] \ + [file dirname $::tcl::clock::LibDir] \ + ] { # now from unix, win, Release: - if {$::tcl_platform(platform) ne {windows}} { - set lib "unix" - set name "lib" - } else { - set lib "win" - set name "" - } - append name tclclockmod * [info sharedlibextension] foreach lib [list {} Release* $lib [file join $lib Release*]] { #puts "**** try $plib / $lib -- [file join $plib $lib $name]" set lib [glob -nocomplain [file join $plib $lib $name]] diff --git a/tests-perf/clock.perf.tcl b/tests-perf/clock.perf.tcl index e11a1fb..ec25447 100644 --- a/tests-perf/clock.perf.tcl +++ b/tests-perf/clock.perf.tcl @@ -127,7 +127,7 @@ proc test-format {{reptime 1000}} { } proc test-scan {{reptime 1000}} { - _test_run $reptime { + _test_run -convert-result {clock format $_(r) -locale en} $reptime { # Scan : date (in gmt) {clock scan "25.11.2015" -format "%d.%m.%Y" -base 0 -gmt 1} # Scan : date (system time zone, with base) @@ -203,11 +203,11 @@ proc test-scan {{reptime 1000}} { # {clock scan "25.11.2015" -format [string repeat "[incr i] %d.%m.%Y %d.%m.%Y" 10] -base 0 -gmt 1} # # Scan : again: # {clock scan "25.11.2015" -format [string repeat "[incr i -1] %d.%m.%Y %d.%m.%Y" 10] -base 0 -gmt 1} - } {puts [clock format $_(r) -locale en]} + } } proc test-freescan {{reptime 1000}} { - _test_run $reptime { + _test_run -convert-result {clock format $_(r) -locale en} $reptime { # FreeScan : relative date {clock scan "5 years 18 months 385 days" -base 0 -gmt 1} # FreeScan : relative date with relative weekday @@ -244,7 +244,7 @@ proc test-freescan {{reptime 1000}} { {clock scan "19:18:30 MST" -base 148863600 -gmt 1 clock scan "19:18:30 EST" -base 148863600 } - } {puts [clock format $_(r) -locale en]} + } } proc test-add {{reptime 1000}} { @@ -287,7 +287,7 @@ proc test-add {{reptime 1000}} { if {[catch {clock add 0 3 weekdays -gmt 1}]} { regsub -all {\mweekdays\M} $tests "days" tests } - _test_run $reptime $tests {puts [clock format $_(r) -locale en]} + _test_run -convert-result {clock format $_(r) -locale en} $reptime $tests } proc test-convert {{reptime 1000}} { @@ -361,10 +361,16 @@ proc test-other {{reptime 1000}} { # Scan : julian day (overflow) {catch {clock scan 5373485 -format %J}} + setup {set _(org-reptime) $_(reptime); lset _(reptime) 1 50} + # Scan : test rotate of GC objects (format is dynamic, so tcl-obj removed with last reference) - {set i 0; time { clock scan "[incr i] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1 } 50} + setup {set i -1} + {clock scan "[incr i] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1} # Scan : test reusability of GC objects (format is dynamic, so tcl-obj removed with last reference) - {set i 50; time { clock scan "[incr i -1] - 25.11.2015" -format "$i - %d.%m.%Y" -base 0 -gmt 1 } 50} + setup {incr i; set j $i} + {clock scan "[incr j -1] - 25.11.2015" -format "$j - %d.%m.%Y" -base 0 -gmt 1} + setup {set _(reptime) $_(org-reptime); set j $i} + {clock scan "[incr j -1] - 25.11.2015" -format "$j - %d.%m.%Y" -base 0 -gmt 1; if {!$j} {set j $i}} } } diff --git a/tests-perf/test-performance.tcl b/tests-perf/test-performance.tcl index b0cbb17..e44ec50 100644 --- a/tests-perf/test-performance.tcl +++ b/tests-perf/test-performance.tcl @@ -1,19 +1,34 @@ # ------------------------------------------------------------------------ # # test-performance.tcl -- -# +# # This file provides common performance tests for comparison of tcl-speed # degradation or regression by switching between branches. # # To execute test case evaluate direct corresponding file "tests-perf\*.perf.tcl". # # ------------------------------------------------------------------------ -# +# # Copyright (c) 2014 Serg G. Brester (aka sebres) -# +# # See the file "license.terms" for information on usage and redistribution # of this file. -# +# + +# check test performance framework is available in tcl-core: +apply {{} { + foreach lib [list \ + [file join [info library] tests-perf test-performance.tcl] \ + [file join [file dirname [info library]] tests-perf test-performance.tcl] \ + ] { + if {[file exists $lib]} { + source $lib + return -code return {}; # found/loaded - use it + } + } +}} + +# last known version of the framework: namespace eval ::tclTestPerf { # warm-up interpeter compiler env, calibrate timerate measurement functionality: @@ -33,7 +48,7 @@ if {[lindex [timerate {} 10] 6] >= (10-1)} { } proc {**STOP**} {args} { - return -code error -level 4 "**STOP** in [info level [expr {[info level]-2}]] [join $args { }]" + return -code error -level 4 "**STOP** in [info level [expr {[info level]-2}]] [join $args { }]" } proc _test_get_commands {lst} { @@ -51,13 +66,13 @@ proc _test_out_total {} { set mintm 0x7fffffff set maxtm 0 - set nett 0 + set nettm 0 set wtm 0 set wcnt 0 set i 0 foreach tm $_(itm) { if {[llength $tm] > 6} { - set nett [expr {$nett + [lindex $tm 6]}] + set nettm [expr {$nettm + [lindex $tm 6]}] } set wtm [expr {$wtm + [lindex $tm 0]}] set wcnt [expr {$wcnt + [lindex $tm 2]}] @@ -69,15 +84,15 @@ proc _test_out_total {} { puts [string repeat ** 40] set s [format "%d cases in %.2f sec." $tcnt [expr {([clock milliseconds] - $_(starttime)) / 1000.0}]] - if {$nett > 0} { - append s [format " (%.2f nett-sec.)" [expr {$nett / 1000.0}]] + if {$nettm > 0} { + append s [format " (%.2f net-sec.)" [expr {$nettm / 1000.0}]] } puts "Total $s:" lset _(m) 0 [format %.6f $wtm] lset _(m) 2 $wcnt - lset _(m) 4 [format %.3f [expr {$wcnt / (($nett ? $nett : ($tcnt * $_(reptime))) / 1000.0)}]] + lset _(m) 4 [format %.3f [expr {$wcnt / (($nettm ? $nettm : ($tcnt * [lindex $_(reptime) 0])) / 1000.0)}]] if {[llength $_(m)] > 6} { - lset _(m) 6 [format %.3f $nett] + lset _(m) 6 [format %.3f $nettm] } puts $_(m) puts "Average:" @@ -94,28 +109,106 @@ proc _test_out_total {} { puts [lindex $_(itm) $maxi] puts [string repeat ** 40] puts "" + unset -nocomplain _(itm) _(starttime) } -proc _test_run {reptime lst {outcmd {puts $_(r)}}} { +proc _test_start {reptime} { upvar _ _ - array set _ [list itm {} reptime $reptime starttime [clock milliseconds]] + array set _ [list itm {} reptime $reptime starttime [clock milliseconds] -from-run 0] +} +proc _test_iter {args} { + if {[llength $args] > 2} { + return -code error "wrong # args: should be \"[lindex [info level [info level]] 0] ?level? measure-result\"" + } + set lvl 1 + if {[llength $args] > 1} { + set args [lassign $args lvl] + } + upvar $lvl _ _ + puts [set _(m) {*}$args] + lappend _(itm) $_(m) + puts "" +} + +proc _adjust_maxcount {reptime maxcount} { + if {[llength $reptime] > 1} { + lreplace $reptime 1 1 [expr {min($maxcount,[lindex $reptime 1])}] + } else { + lappend reptime $maxcount + } +} + +proc _test_run {args} { + upvar _ _ + # parse args: + array set _ {-no-result 0 -uplevel 0 -convert-result {}} + while {[llength $args] > 2} { + if {![info exists _([set o [lindex $args 0]])]} { + break + } + if {[string is boolean -strict $_($o)]} { + set _($o) [expr {! $_($o)}] + set args [lrange $args 1 end] + } else { + if {[llength $args] <= 2} { + return -code error "value expected for option $o" + } + set _($o) [lindex $args 1] + set args [lrange $args 2 end] + } + } + unset -nocomplain o + if {[llength $args] < 2 || [llength $args] > 3} { + return -code error "wrong # args: should be \"[lindex [info level [info level]] 0] ?-no-result? reptime lst ?outcmd?\"" + } + set _(outcmd) {puts} + set args [lassign $args reptime lst] + if {[llength $args]} { + set _(outcmd) [lindex $args 0] + } + # avoid output if only once: + if {[lindex $reptime 0] <= 1 || ([llength $reptime] > 1 && [lindex $reptime 1] == 1)} { + set _(-no-result) 1 + } + if {![info exists _(itm)]} { + array set _ [list itm {} reptime $reptime starttime [clock milliseconds] -from-run 1] + } else { + array set _ [list reptime $reptime] + } + + # process measurement: foreach _(c) [_test_get_commands $lst] { - puts "% [regsub -all {\n[ \t]*} $_(c) {; }]" + {*}$_(outcmd) "% [regsub -all {\n[ \t]*} $_(c) {; }]" if {[regexp {^\s*\#} $_(c)]} continue if {[regexp {^\s*(?:setup|cleanup)\s+} $_(c)]} { - puts [if 1 [lindex $_(c) 1]] + set _(c) [lindex $_(c) 1] + if {$_(-uplevel)} { + set _(c) [list uplevel 1 $_(c)] + } + {*}$_(outcmd) [if 1 $_(c)] continue } - if {$reptime > 1} {; #if not once: + if {$_(-uplevel)} { + set _(c) [list uplevel 1 $_(c)] + } + set _(ittime) $_(reptime) + # if output result (and not once): + if {!$_(-no-result)} { set _(r) [if 1 $_(c)] - if {$outcmd ne {}} $outcmd + if {$_(-convert-result) ne ""} { set _(r) [if 1 $_(-convert-result)] } + {*}$_(outcmd) $_(r) + if {[llength $_(ittime)] > 1} { # decrement max-count + lset _(ittime) 1 [expr {[lindex $_(ittime) 1] - 1}] + } } - puts [set _(m) [timerate $_(c) $reptime]] + {*}$_(outcmd) [set _(m) [timerate $_(c) {*}$_(ittime)]] lappend _(itm) $_(m) - puts "" + {*}$_(outcmd) "" + } + if {$_(-from-run)} { + _test_out_total } - _test_out_total } }; # end of namespace ::tclTestPerf diff --git a/tests/all.tcl b/tests/all.tcl index 7704195..640c868 100644 --- a/tests/all.tcl +++ b/tests/all.tcl @@ -30,12 +30,12 @@ proc ::tcltest::__ReportSummary {} { } puts [outputChannel] "" } -proc ::tcltest::__ReportToMaster {total passed skipped failed skippedLst args} { +proc ::tcltest::__ReportToParent {total passed skipped failed skippedLst args} { array set ::TestSummary [list \ Total $total Passed $passed Skipped $skipped Failed $failed skippedBecauseLst $skippedLst args $args] incr ::TestSummary(TotFailed) $failed ::tcltest::__ReportSummary - ::tcltest::ReportedFromSlave $total $passed $skipped $failed $skippedLst {*}$args + ::tcltest::ReportedFromChild $total $passed $skipped $failed $skippedLst {*}$args } proc ::tcltest::__SortFiles {lst} { set slst {} @@ -50,6 +50,7 @@ proc ::tcltest::__SortFiles {lst} { } set TESTDIR [file normalize [file dirname [info script]]] +set BUILDDIR [pwd] if {![::tcl::pkgconfig get debug]} { # allow to load lib from current directory in debug: # switch to temp directory: if {[catch { @@ -75,8 +76,9 @@ foreach testfile [::tcltest::__SortFiles [::tcltest::GetMatchingFiles $TESTDIR]] set slave [interp create] interp eval $slave [package ifneeded tcltest $tcltest::Version] $slave eval {namespace import tcltest::*} - interp alias $slave ::tcltest::ReportToMaster {} ::tcltest::__ReportToMaster + interp alias $slave ::tcltest::ReportToParent {} ::tcltest::__ReportToParent $slave eval [list set TESTFILE [file join $TESTDIR $testfile]] + $slave eval [list set BUILDDIR $BUILDDIR] $slave eval [list ::tcltest::configure {*}$TEST_OPTIONS] $slave eval $GLOB_OPTIONS # invoke test suite: diff --git a/tests/clock.test b/tests/clock.test index b2c8460..78b118e 100644 --- a/tests/clock.test +++ b/tests/clock.test @@ -12,8 +12,8 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. -if {[lsearch [namespace children] ::tcltest] == -1} { - package require tcltest 2 +if {"::tcltest" ni [namespace children]} { + package require tcltest 2.5 namespace import -force ::tcltest::* } @@ -39,16 +39,19 @@ testConstraint no_tclclockmod \ set valid_mode [clock configure -valid] # Wrapper to show validity mode in the test-case name (for possible errors): -rename test __test proc test {args} { variable valid_mode lset args 0 [lindex $args 0].vm:$valid_mode - uplevel [linsert $args [set args 0] __test] + tailcall ::tcltest::test {*}$args } puts [outputChannel] " Validity default mode: [expr {$valid_mode ? "on": "off"}]" testConstraint valid_off [expr {![clock configure -valid]}] +if {[namespace which -command ::tcl::unsupported::timerate] ne ""} { + namespace import ::tcl::unsupported::timerate +} + # TEST PLAN # clock-0: @@ -254,7 +257,7 @@ namespace eval ::testClock { Bias 300 \ StandardBias 0 \ DaylightBias -60 \ - StandardStart \x00\x00\x0b\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00 \ + StandardStart \x00\x00\x0B\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00 \ DaylightStart \x00\x00\x03\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00]] } @@ -281,16 +284,30 @@ test clock-0.1 "initial: auto-loading of ensemble and stubs on demand" no_tclclo clock seconds; # init ensemble (but not yet stubs, loading of clock.tcl retarded) lappend ret ens:[namespace ensemble exists ::clock] lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] - clock format -now; # clock.tcl stubs expected + clock format now; # clock.tcl stubs expected lappend ret stubs:[expr {[namespace which -command ::tcl::clock::GetSystemTimeZone] ne ""}] }] interp delete $i set ret } {ens:0 ens:1 stubs:0 stubs:1} +test clock-0.2 "initial: loading of format/locale does not overwrite interp state (errorInfo)" -setup { + # be sure - we have no cached locale/msgcat, etc: + if {[namespace which -command ::tcl::clock::ClearCaches] ne ""} { + ::tcl::clock::ClearCaches + } +} -body { + if {[catch { + return -level 0 -code error -errorcode {EXPERR TEST-ERROR} -errorinfo "ERROR expected error" test + }]} { + clock format now -locale de; # should not overwrite error code/info + list $::errorCode $::errorInfo + } +} -result {{EXPERR TEST-ERROR} {ERROR expected error}} + # Test some of the basics of [clock format] -set syntax "clock format clockval|-now ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?" +set syntax "clock format clockval|now ?-format string? ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?" test clock-1.0 "clock format - wrong # args" { list [catch {clock format} msg] $msg $::errorCode } [subst {1 {wrong # args: should be "$syntax"} {CLOCK wrongNumArgs}}] @@ -301,7 +318,7 @@ test clock-1.0.1 "clock format - wrong # args (compiled ensemble with invalid sy test clock-1.1 "clock format - bad time" { list [catch {clock format foo} msg] $msg -} {1 {expected integer but got "foo"}} +} {1 {bad seconds "foo": must be now or integer}} test clock-1.2 "clock format - bad gmt val" { list [catch {clock format 0 -gmt foo} msg] $msg @@ -320,9 +337,18 @@ test clock-1.4.1 "clock format - unexpected option for this sub-command" { list [catch {clock format 0 -base 0} msg] $msg $::errorCode } [subst {1 {bad option "-base": should be "$syntax"} {CLOCK badOption -base}}] -test clock-1.5 "clock format - bad timezone" { - list [catch {clock format 0 -format "%s" -timezone :NOWHERE} msg] $msg $::errorCode -} {1 {time zone ":NOWHERE" not found} {CLOCK badTimeZone :NOWHERE}} +test clock-1.5 "clock format - bad timezone (not found)" -body { + clock format 0 -format "%s" -timezone :NOWHERE +} -returnCodes 1 -result {time zone ":NOWHERE" not found} -errorCode {CLOCK badTimeZone :NOWHERE} +foreach tz [list {*}{ + ../UNSAFEPATH/NOWHERE UNSAFEPATH/../GMT //UNSAFEPATH/NOWHERE + zipfs:/UNSAFEPATH/NOWHERE C:/UNSAFEPATH/NOWHERE + } [list $::tcl::clock::DataDir/GMT] +] { +test clock-1.5.1 "clock format - bad timezone (not valid - unsafe path)" -body { + clock format 0 -format "%s" -timezone $tz +} -returnCodes 1 -result "time zone \":$tz\" not valid" -errorCode [list CLOCK badTimeZone :$tz] +} test clock-1.6 "clock format - gmt + timezone" { list [catch {clock format 0 -timezone :GMT -gmt true} msg] $msg $::errorCode @@ -332,16 +358,24 @@ test clock-1.7 "clock format - option abbreviations" { clock format 0 -g true -f "%Y-%m-%d" } 1970-01-01 -test clock-1.8 "clock format -now" { +test clock-1.7.1 "clock format - command abbreviations (compat regression test)" { + clock f 0 -g 1 -f "%Y-%m-%d" +} 1970-01-01 + +test clock-1.8 "clock format now" { # give one second more for test (if on boundary of the current second): set n [clock format [clock seconds] -g 1 -f "%s"] - expr {[clock format -now -g 1 -f "%s"] in [list $n [incr n]]} + expr {[clock format now -g 1 -f "%s"] in [list $n [incr n]]} } 1 test clock-1.9 "clock arguments: option doubly present" { list [catch {clock format 0 -gmt 1 -gmt 0} result] $result } {1 {bad option "-gmt": doubly present}} +test clock-1.10 {clock format: text with token (bug [a858d95f4bfddafb])} { + clock format 0 -format text(%d) -gmt 1 +} {text(01)} + # BEGIN testcases2 # Test formatting of Gregorian year, month, day, all formats @@ -15345,6 +15379,84 @@ test clock-4.96 { format time of day 23:59:59 } { -locale en_US_roman \ -gmt true } {23 xxiii 11 xi 23 xxiii 11 xi 59 lix PM pm 11:59:59 pm 23:59 59 lix 23:59:59 23:59:59 xxiii h lix m lix s Thu Jan 1 23:59:59 GMT 1970} + +test clock-4.97.1 { format JDN/JD (calendar and astronomical) } { + clock format 0 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.0 2440587.5} +test clock-4.97.2 { format JDN/JD (calendar and astronomical) } { + clock format 43200 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.5 2440588.0} +test clock-4.97.3 { format JDN/JD (calendar and astronomical) } { + clock format 86399 -format {%J %EJ %Ej} -gmt true +} {2440588 2440588.99998843 2440588.49998843} +test clock-4.97.4 { format JDN/JD (calendar and astronomical) } { + clock format 86400 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.0 2440588.5} +test clock-4.97.5 { format JDN/JD (calendar and astronomical) } { + clock format 129599 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.49998843 2440588.99998843} +test clock-4.97.6 { format JDN/JD (calendar and astronomical) } { + clock format 129600 -format {%J %EJ %Ej} -gmt true +} {2440589 2440589.5 2440589.0} +test clock-4.97.7 { format JDN/JD (calendar and astronomical) } { + set i 1548249092 + list \ + [clock format $i -format {%J %EJ %Ej} -gmt true] \ + [clock format [incr i] -format {%J %EJ %Ej} -gmt true] \ + [clock format [incr i] -format {%J %EJ %Ej} -gmt true] +} {{2458507 2458507.54967593 2458507.04967593} {2458507 2458507.5496875 2458507.0496875} {2458507 2458507.54969907 2458507.04969907}} +test clock-4.97.8 { format JDN/JD (calendar and astronomical) } { + set res {} + foreach i { + -172800 -129600 -86400 -43200 + -1 0 1 21600 43199 43200 86399 + 86400 86401 108000 129600 172800 + } { + lappend res $i [clock format [expr {-210866803200 - $i}] \ + -format {%EE %Y-%m-%d %T -- %J %EJ %Ej} -gmt true] + } + set res +} [list \ + -172800 {B.C.E. 4713-01-03 00:00:00 -- 0000002 2.0 1.5} \ + -129600 {B.C.E. 4713-01-02 12:00:00 -- 0000001 1.5 1.0} \ + -86400 {B.C.E. 4713-01-02 00:00:00 -- 0000001 1.0 0.5} \ + -43200 {B.C.E. 4713-01-01 12:00:00 -- 0000000 0.5 0.0} \ + -1 {B.C.E. 4713-01-01 00:00:01 -- 0000000 0.00001157 -0.49998843} \ + 0 {B.C.E. 4713-01-01 00:00:00 -- 0000000 0.0 -0.5} \ + 1 {B.C.E. 4714-12-31 23:59:59 -- -000001 -0.00001157 -0.50001157} \ + 21600 {B.C.E. 4714-12-31 18:00:00 -- -000001 -0.25 -0.75} \ + 43199 {B.C.E. 4714-12-31 12:00:01 -- -000001 -0.49998843 -0.99998843} \ + 43200 {B.C.E. 4714-12-31 12:00:00 -- -000001 -0.5 -1.0} \ + 86399 {B.C.E. 4714-12-31 00:00:01 -- -000001 -0.99998843 -1.49998843} \ + 86400 {B.C.E. 4714-12-31 00:00:00 -- -000001 -1.0 -1.5} \ + 86401 {B.C.E. 4714-12-30 23:59:59 -- -000002 -1.00001157 -1.50001157} \ + 108000 {B.C.E. 4714-12-30 18:00:00 -- -000002 -1.25 -1.75} \ + 129600 {B.C.E. 4714-12-30 12:00:00 -- -000002 -1.5 -2.0} \ + 172800 {B.C.E. 4714-12-30 00:00:00 -- -000002 -2.0 -2.5} \ +] +test clock-4.97.9 { format JDN/JD (calendar and astronomical) } { + set res {} + foreach i { + -86400 -43200 + -1 0 1 + 43199 43200 43201 86400 + } { + lappend res $i [clock format [expr {653133196800 + $i}] \ + -format {%Y-%m-%d %T -- %J %EJ %Ej} -gmt true] + } + set res +} [list \ + -86400 {22666-12-19 00:00:00 -- 9999999 9999999.0 9999998.5} \ + -43200 {22666-12-19 12:00:00 -- 9999999 9999999.5 9999999.0} \ + -1 {22666-12-19 23:59:59 -- 9999999 9999999.99998843 9999999.49998843} \ + 0 {22666-12-20 00:00:00 -- 10000000 10000000.0 9999999.5} \ + 1 {22666-12-20 00:00:01 -- 10000000 10000000.00001157 9999999.50001157} \ + 43199 {22666-12-20 11:59:59 -- 10000000 10000000.49998843 9999999.99998843} \ + 43200 {22666-12-20 12:00:00 -- 10000000 10000000.5 10000000.0} \ + 43201 {22666-12-20 12:00:01 -- 10000000 10000000.50001157 10000000.00001157} \ + 86400 {22666-12-21 00:00:00 -- 10000001 10000001.0 10000000.5} \ +] + # END testcases4 # BEGIN testcases5 @@ -18586,13 +18698,36 @@ test clock-6.8 {input of seconds} { } 9223372036854775807 test clock-6.9 {input of seconds - overflow} { - list [catch {clock scan -9223372036854775809 -format %s -gmt true} result] $result $::errorCode + list [catch {clock scan -9223372036854775809 -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] } {1 {requested date too large to represent} {CLOCK dateTooLarge}} - test clock-6.10 {input of seconds - overflow} { - list [catch {clock scan 9223372036854775808 -format %s -gmt true} result] $result $::errorCode + list [catch {clock scan 9223372036854775808 -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] } {1 {requested date too large to represent} {CLOCK dateTooLarge}} +foreach sign {{} -} { + test clock-6.10a {input of seconds - overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}27670116110564327423 -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } {1 {requested date too large to represent} {CLOCK dateTooLarge}} + test clock-6.10b {input of seconds - overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}27670116110564327424 -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } {1 {requested date too large to represent} {CLOCK dateTooLarge}} + test clock-6.10c {input of seconds - no overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}[string repeat 9 18] -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } [list 0 ${sign}[string repeat 9 18] {}] + test clock-6.10d {input of seconds - overflow, bug [1f40aa83c5]} { + list [catch {clock scan ${sign}[string repeat 9 19] -format %s -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } {1 {requested date too large to represent} {CLOCK dateTooLarge}} + # both fololowing freescan test don't generate overflow error, + # since it is a free scan, thus the token is simply not recognized further in yacc lexer, + # therefore we get parse error (can be surely changed latter): + test clock-6.10e {input of seconds - overflow (but since freescan parse error, but not boom), bug [1f40aa83c5]} -body { + list [catch {clock scan ${sign}27670116110564327423 -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } -match glob -result {1 {unable to convert date-time string "*": syntax error *} {TCL VALUE DATE PARSE}} + test clock-6.10f {input of seconds - overflow (but since freescan parse error, but not boom), bug [1f40aa83c5]} -body { + list [catch {clock scan ${sign}27670116110564327424 -gmt true} result opt] $result [if {[dict exists $opt -errorcode]} {dict get $opt -errorcode}] + } -match glob -result {1 {unable to convert date-time string "*": syntax error *} {TCL VALUE DATE PARSE}} +}; unset sign + test clock-6.11 {input of seconds - two values} { clock scan {1 2} -format {%s %s} -gmt true } 2 @@ -18786,6 +18921,10 @@ test clock-6.22.20 {Greedy match (second space wins as date-time separator)} { clock format [clock scan "111 2 13120" -format "%y%m%d %H%M%S" -gmt 1] -locale en -gmt 1 } {Sun Jan 02 13:12:00 GMT 2011} +test clock-6.23 {clock scan: text with token (bug [a858d95f4bfddafb])} { + clock scan {text(01)} -format text(%d) -gmt 1 -base 0 +} 0 + test clock-7.1 {Julian Day} { clock scan 0 -format %J -gmt true @@ -18840,6 +18979,105 @@ test clock-7.9 {Julian Day, two values} { clock scan {2440588 2440589} -format {%J %J} -gmt true } 86400 +test clock-7.10 {Julian Day, negative amount} { + # Note: %J does not accept negative input; + # add negative amounts to Julian day 0 instead + set s0 [clock scan 0 -format %J -gmt true] + set J0 [scan [clock format $s0 -format %J -gmt true] %lld] + set s0m1d [clock add $s0 -1 days -timezone :UTC] + set s0m24h [clock add $s0 -24 hours -timezone :UTC] + set J0m24h [scan [clock format $s0m24h -format %J -gmt true] %lld] + set s0m1s [clock add $s0 -1 seconds -timezone :UTC] + set J0m1s [scan [clock format $s0m1s -format %J -gmt true] %lld] + list $s0m1d $s0m24h $J0m24h $s0m1s $J0m1s $s0 $J0 \ + [::tcl::mathop::== $s0m1d $s0m24h] [::tcl::mathop::== $J0m24h $J0m1s] +} [list -210866889600 -210866889600 -1 -210866803201 -1 -210866803200 0 1 1] + +test clock-7.11.1 {Calendar vs Astronomical Julian Day (without and with time fraction)} { + list \ + [clock scan {2440588} -format {%J} -gmt true] \ + [clock scan {2440588} -format {%EJ} -gmt true] \ + [clock scan {2440588} -format {%Ej} -gmt true] \ + [clock scan {2440588.5} -format {%EJ} -gmt true] \ + [clock scan {2440588.5} -format {%Ej} -gmt true] \ +} {0 0 43200 43200 86400} + +test clock-7.11.2 {Astronomical JDN/JD} { + clock scan 0 -format %Ej -gmt true +} -210866760000 + +test clock-7.12 {Astronomical JDN/JD} { + clock format [clock scan 2440587.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "1970-01-01 00:00:00" + +test clock-7.13 {Astronomical JDN/JD} { + clock format [clock scan 2451544.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "2000-01-01 00:00:00" + +test clock-7.13.1 {Astronomical JDN/JD} { + clock format [clock scan 2488069.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "2100-01-01 00:00:00" + +test clock-7.14 {Astronomical JDN/JD} { + clock format [clock scan 5373483.5 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 00:00:00" + +test clock-7.14.1 {Astronomical JDN/JD} { + clock format [clock scan 5373484 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 12:00:00" +test clock-7.14.2 {Astronomical JDN/JD} { + clock format [clock scan 5373484.49999 -format %Ej -gmt true] \ + -format "%Y-%m-%d %T" -gmt true +} "9999-12-31 23:59:59" + +test clock-7.15 {Astronomical JDN/JD, bad} { + list [catch { + clock scan bogus -format %Ej + } result] $result $errorCode +} {1 {input string does not match supplied format} {CLOCK badInputString}} + +test clock-7.16 {Astronomical JDN/JD, overflow} { + list [catch { + clock scan 5373484.5 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 5373485 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 2147483648 -format %Ej + } result] $result $errorCode \ + [catch { + clock scan 2147483648.5 -format %Ej + } result] $result $errorCode +} [lrepeat 4 1 {requested date too large to represent} {CLOCK dateTooLarge}] + +test clock-7.18 {Astronomical JDN/JD, same precedence as seconds (last wins} { + list [clock scan {2440588 86400} -format {%Ej %s} -gmt true] \ + [clock scan {2440589 0} -format {%Ej %s} -gmt true] \ + [clock scan {86400 2440588} -format {%s %Ej} -gmt true] \ + [clock scan {0 2440589} -format {%s %Ej} -gmt true] +} {86400 0 43200 129600} + +test clock-7.19 {Astronomical JDN/JD, two values} { + clock scan {2440588 2440589} -format {%Ej %Ej} -gmt true +} 129600 + +test clock-7.20 {all JDN/JD are signed (and extended accept floats)} { + set res {} + foreach i {%J %EJ %Ej} { + lappend res [clock scan "-1" -format $i -gmt 1] + } + foreach i {%EJ %Ej} { + lappend res [clock scan "-1.5" -format $i -gmt 1] + } + set res +} {-210866889600 -210866889600 -210866846400 -210866846400 -210866803200} + # BEGIN testcases8 # Test parsing of ccyymmdd @@ -21250,9 +21488,22 @@ test clock-9.1 {seconds take precedence over ccyymmdd} { clock scan {0 20000101} -format {%s %Y%m%d} -gmt true } 0 -test clock-9.2 {Julian day takes precedence over ccyymmdd} { - clock scan {2440588 20000101} -format {%J %Y%m%d} -gmt true -} 0 +test clock-9.2 {Calendar julian day takes precedence over ccyymmdd} { + list \ + [clock scan {2440588 20000101} -format {%J %Y%m%d} -gmt true] \ + [clock scan {2440588 20000101} -format {%EJ %Y%m%d} -gmt true] +} {0 0} +test clock-9.2.1 {Calendar julian day (with time fraction) takes precedence over date-time} { + list \ + [clock scan {2440588.0 20000101 010203} -format {%EJ %Y%m%d %H%M%S} -gmt true] \ + [clock scan {2440588.5 20000101 010203} -format {%EJ %Y%m%d %H%M%S} -gmt true] + +} {0 43200} +test clock-9.3 {Astro julian day takes always precedence over date-time} { + list \ + [clock scan {2440587.5 20000101 010203} -format {%Ej %Y%m%d %H%M%S} -gmt true] \ + [clock scan {2440588 20000101 010203} -format {%Ej %Y%m%d %H%M%S} -gmt true] +} {0 43200} # Test parsing of ccyyddd @@ -35547,6 +35798,44 @@ test clock-30.30 {clock add weekdays and back} -body { } return "OK" } -result {OK} +test clock-30.31 {regression test - add no int overflow} { + list \ + [list \ + [clock add 0 1600000000 seconds 24856 days -gmt 1] \ + [clock add 0 1600000000 seconds 815 months -gmt 1] \ + [clock add 0 1600000000 seconds 69 years -gmt 1] \ + [clock add 0 1600000000 seconds 596524 hours -gmt 1] \ + [clock add 0 1600000000 seconds 35791395 minutes -gmt 1] \ + [clock add 0 1600000000 seconds 0x7fffffff seconds -gmt 1] + ] \ + [list \ + [clock add 1600000000 24856 days -gmt 1] \ + [clock add 1600000000 815 months -gmt 1] \ + [clock add 1600000000 69 years -gmt 1] \ + [clock add 1600000000 596524 hours -gmt 1] \ + [clock add 1600000000 35791395 minutes -gmt 1] \ + [clock add 1600000000 0x7fffffff seconds -gmt 1] + ] +} [lrepeat 2 {3747558400 3743238400 3777452800 3747486400 3747483700 3747483647}] +test clock-30.32 {regression test - add no int overflow} { + list \ + [list \ + [clock add 3777452800 -1600000000 seconds -24856 days -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -815 months -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -69 years -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -596524 hours -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -35791395 minutes -gmt 1] \ + [clock add 3777452800 -1600000000 seconds -0x7fffffff seconds -gmt 1] + ] \ + [list \ + [clock add 2177452800 -24856 days -gmt 1] \ + [clock add 2177452800 -815 months -gmt 1] \ + [clock add 2177452800 -69 years -gmt 1] \ + [clock add 2177452800 -596524 hours -gmt 1] \ + [clock add 2177452800 -35791395 minutes -gmt 1] \ + [clock add 2177452800 -0x7fffffff seconds -gmt 1] + ] +} [lrepeat 2 {29894400 34214400 0 29966400 29969100 29969153}] # END testcases30 @@ -35771,20 +36060,20 @@ test clock-32.1 {scan/format across the Gregorian change} { # clock clicks test clock-33.1 {clock clicks tests} { - expr [clock clicks]+1 + expr {[clock clicks] + 1} concat {} } {} test clock-33.2 {clock clicks tests} { set start [clock clicks] after 10 set end [clock clicks] - expr "$end > $start" + expr {$end > $start} } {1} test clock-33.3 {clock clicks tests} { list [catch {clock clicks foo} msg] $msg } {1 {bad option "foo": must be -milliseconds or -microseconds}} test clock-33.4 {clock clicks tests} { - expr [clock clicks -milliseconds]+1 + expr {[clock clicks -milliseconds] + 1} concat {} } {} test clock-33.4a {clock milliseconds} { @@ -35794,26 +36083,34 @@ test clock-33.4a {clock milliseconds} { test clock-33.5 {clock clicks tests, millisecond timing test} { # This test can fail on a system that is so heavily loaded that # the test takes >60 ms to run. - set start [clock clicks -milli] - after 10 - set end [clock clicks -milli] + if {[lindex [timerate { + set start [clock clicks -milli] + timerate {} 10; # short but precise busy wait + set end [clock clicks -milli] + } 1 1] 0] > 60000} { + ::tcltest::Skip "timing issue" + } # 60 msecs seems to be the max time slice under Windows 95/98 expr { ($end > $start) && (($end - $start) <= 60) ? "ok" : - "test should have taken 0-60 ms, actually took [expr $end - $start]"} + "test should have taken 0-60 ms, actually took [expr {$end - $start}]"} } {ok} test clock-33.5a {clock tests, millisecond timing test} { # This test can fail on a system that is so heavily loaded that # the test takes >60 ms to run. - set start [clock milliseconds] - after 10 - set end [clock milliseconds] + if {[lindex [timerate { + set start [clock milliseconds] + timerate {} 10; # short but precise busy wait + set end [clock milliseconds] + } 1 1] 0] > 60000} { + ::tcltest::Skip "timing issue" + } # 60 msecs seems to be the max time slice under Windows 95/98 expr { ($end > $start) && (($end - $start) <= 60) ? "ok" : - "test should have taken 0-60 ms, actually took [expr $end - $start]"} + "test should have taken 0-60 ms, actually took [expr {$end - $start}]"} } {ok} test clock-33.6 {clock clicks, milli with too much abbreviation} { list [catch { clock clicks ? } msg] $msg @@ -35825,17 +36122,25 @@ test clock-33.7 {clock clicks, milli with too much abbreviation} { test clock-33.8 {clock clicks test, microsecond timing test} { # This test can fail on a system that is so heavily loaded that # the test takes >60 ms to run. - set start [clock clicks -micro] - after 10 - set end [clock clicks -micro] + if {[lindex [timerate { + set start [clock clicks -micro] + timerate {} 10; # short but precise busy wait + set end [clock clicks -micro] + } 1 1] 0] > 60000} { + ::tcltest::Skip "timing issue" + } expr {($end > $start) && (($end - $start) <= 60000)} } {1} test clock-33.8a {clock test, microsecond timing test} { # This test can fail on a system that is so heavily loaded that # the test takes >60 ms to run. - set start [clock microseconds] - after 10 - set end [clock microseconds] + if {[lindex [timerate { + set start [clock microseconds] + timerate {} 10; # short but precise busy wait + set end [clock microseconds] + } 1 1] 0] > 60000} { + ::tcltest::Skip "timing issue" + } expr {($end > $start) && (($end - $start) <= 60000)} } {1} @@ -35960,55 +36265,115 @@ test clock-34.11.3 {clock scan tests: same century switch} { set times [clock scan "1/1/39" -gmt true] } [clock scan "1/1/39" -format "%m/%d/%y" -gmt true] test clock-34.12 {clock scan, relative times} { - set time [clock scan "Oct 23, 1992 -1 day"] - clock format $time -format {%b %d, %Y} + set time [clock scan "Oct 23, 1992 -1 day" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 22, 1992" + test clock-34.13 {clock scan, ISO 8601 base date format} { - set time [clock scan "19921023"] - clock format $time -format {%b %d, %Y} + set time [clock scan "19921023" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.14 {clock scan, ISO 8601 expanded date format} { - set time [clock scan "1992-10-23"] - clock format $time -format {%b %d, %Y} + set time [clock scan "1992-10-23" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.15 {clock scan, DD-Mon-YYYY format} { - set time [clock scan "23-Oct-1992"] - clock format $time -format {%b %d, %Y} + set time [clock scan "23-Oct-1992" -gmt true] + clock format $time -format {%b %d, %Y} -gmt true } "Oct 23, 1992" test clock-34.16 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023T235959"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023T235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.16.1a {clock scan, ISO 8601 T literal optional (YYYYMMDDhhmmss)} { + set time [clock scan "19921023235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.16.1b {clock scan, ISO 8601 T literal optional (YYYYMMDDhhmm)} { + set time [clock scan "199210232359" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.16.2 {clock scan, ISO 8601 extended date time} { + set time [clock scan "1992-10-23T23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 23:59:59" test clock-34.17 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023 235959"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023 235959" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2a {clock scan, ISO 8601 extended date time (YYYY-MM-DD hh:mm:ss)} { + set time [clock scan "1992-10-23 23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2b {clock scan, ISO 8601 extended date time (YYYY-MM-DDThh:mm:ss)} { + set time [clock scan "1992-10-23T23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.2c {clock scan, ISO 8601 extended date time (YYYY-MM-DD hh:mm)} { + set time [clock scan "1992-10-23 23:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.17.2d {clock scan, ISO 8601 extended date time (YYYY-MM-DDThh:mm)} { + set time [clock scan "1992-10-23T23:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:00" +test clock-34.17.3 {clock scan, TZ-word boundaries - Z is not TZ here } -body { + set time [clock scan "1992-10-23Z23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} -returnCodes error -match glob \ + -result {unable to convert date-time string*} +test clock-34.17.4 {clock scan, TZ-word boundaries - Z is TZ UTC here} { + set time [clock scan "1992-10-23 Z 23:59:59" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 23:59:59" +test clock-34.17.5 {clock scan, ISO 8601 extended date time with UTC TZ} { + set time [clock scan "1992-10-23T23:59:59Z" -timezone :America/Detroit] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 23:59:59" test clock-34.18 {clock scan, ISO 8601 point in time format} { - set time [clock scan "19921023T000000"] - clock format $time -format {%b %d, %Y %H:%M:%S} + set time [clock scan "19921023T000000" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" +test clock-34.18.2 {clock scan, ISO 8601 extended date time} { + set time [clock scan "1992-10-23T00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" +test clock-34.18.3 {clock scan, TZ-word boundaries - Z is not TZ here } -body { + set time [clock scan "1992-10-23Z00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} -returnCodes error -match glob \ + -result {unable to convert date-time string*} +test clock-34.18.4 {clock scan, TZ-word boundaries - Z is TZ UTC here} { + set time [clock scan "1992-10-23 Z 00:00:00" -gmt true] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true +} "Oct 23, 1992 00:00:00" +test clock-34.18.5 {clock scan, ISO 8601 extended date time with UTC TZ} { + set time [clock scan "1992-10-23T00:00:00Z" -timezone :America/Detroit] + clock format $time -format {%b %d, %Y %H:%M:%S} -gmt true } "Oct 23, 1992 00:00:00" + test clock-34.20.1 {clock scan tests (-TZ)} { - set time [clock scan "31 Jan 14 23:59:59 -0100"] + set time [clock scan "31 Jan 14 23:59:59 -0100" -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Feb 01,2014 00:59:59 GMT} test clock-34.20.2 {clock scan tests (+TZ)} { - set time [clock scan "31 Jan 14 23:59:59 +0100"] + set time [clock scan "31 Jan 14 23:59:59 +0100" -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 31,2014 22:59:59 GMT} test clock-34.20.3 {clock scan tests (-TZ)} { - set time [clock scan "23:59:59 -0100" -base 0] + set time [clock scan "23:59:59 -0100" -base 0 -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 02,1970 00:59:59 GMT} test clock-34.20.4 {clock scan tests (+TZ)} { - set time [clock scan "23:59:59 +0100" -base 0] + set time [clock scan "23:59:59 +0100" -base 0 -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 01,1970 22:59:59 GMT} test clock-34.20.5 {clock scan tests (TZ)} { - set time [clock scan "Mon, 30 Jun 2014 23:59:59 CEST"] + set time [clock scan "Mon, 30 Jun 2014 23:59:59 CEST" -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jun 30,2014 21:59:59 GMT} test clock-34.20.6 {clock scan tests (TZ)} { - set time [clock scan "Fri, 31 Jan 2014 23:59:59 CET"] + set time [clock scan "Fri, 31 Jan 2014 23:59:59 CET" -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 31,2014 22:59:59 GMT} test clock-34.20.7 {clock scan tests (relspec, day unit not TZ)} { @@ -36020,11 +36385,11 @@ test clock-34.20.8 {clock scan tests (relspec, day unit not TZ)} { clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 09,1970 23:59:59 GMT} test clock-34.20.9 {clock scan tests (merid and TZ)} { - set time [clock scan "10:59 pm CET" -base 2000000] + set time [clock scan "10:59 pm CET" -base 2000000 -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 24,1970 21:59:00 GMT} test clock-34.20.10 {clock scan tests (merid and TZ)} { - set time [clock scan "10:59 pm +0100" -base 2000000] + set time [clock scan "10:59 pm +0100" -base 2000000 -gmt true] clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true } {Jan 24,1970 21:59:00 GMT} test clock-34.20.11 {clock scan tests (complex TZ)} { @@ -36080,6 +36445,12 @@ test clock-34.20.19 {clock scan tests (no TZ)} { test clock-34.20.20 {clock scan tests (TZ, TZ + 1day)} { clock scan "00:00 GMT+1000 day" -base 100000000 -gmt 1 } 100015200 +test clock-34.20.21 {clock scan tests (local date of base depends on given TZ, time apllied to different day)} { + list [clock scan "23:59:59 -0100" -base 0 -timezone :CET] \ + [clock scan "23:59:59 -0100" -base 0 -gmt 1] \ + [clock scan "23:59:59 -0100" -base 0 -timezone -1400] \ + [clock scan "23:59:59 -0100" -base 0 -timezone :Pacific/Apia] +} {89999 89999 3599 3599} # CLOCK SCAN REAL TESTS @@ -36225,7 +36596,6 @@ test clock-34.43 {last monday in november} { } set res } {1991-11-25 1992-11-30 1993-11-29 1994-11-28 1995-11-27 1996-11-25} - test clock-34.44 {2nd monday in november} { set res {} foreach i {91 92 93 94 95 96} { @@ -36258,44 +36628,102 @@ test clock-34.47 {ago with multiple relative units} { set res [clock scan "2 days 2 hours ago" -base $base] expr {$base - $res} } 180000 - test clock-34.48 {more than one ToD} {*}{ -body {clock scan {10:00 11:00}} -returnCodes error -result {unable to convert date-time string "10:00 11:00": more than one time of day in string} } - test clock-34.49 {more than one date} {*}{ -body {clock scan {1/1/2001 2/2/2002}} -returnCodes error -result {unable to convert date-time string "1/1/2001 2/2/2002": more than one date in string} } - test clock-34.50 {more than one time zone} {*}{ -body {clock scan {10:00 EST CST}} -returnCodes error -result {unable to convert date-time string "10:00 EST CST": more than one time zone in string} } - test clock-34.51 {more than one weekday} {*}{ -body {clock scan {Monday Tuesday}} -returnCodes error -result {unable to convert date-time string "Monday Tuesday": more than one weekday in string} } - test clock-34.52 {more than one ordinal month} {*}{ -body {clock scan {next January next March}} -returnCodes error -result {unable to convert date-time string "next January next March": more than one ordinal month in string} } +test clock-34.53 {clock scan, ISO 8601 point in time format} { + set time [clock scan "19921023T00:00:00"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} "Oct 23, 1992 00:00:00" +test clock-34.54 {clock scan, ISO 8601 point in time format} { + set time [clock scan "1992-10-23T00:00:00"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} "Oct 23, 1992 00:00:00" +test clock-34.55 {clock scan, ISO 8601 invalid TZ} -body { + set time [clock scan "19921023MST000000"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} -returnCodes error -match glob -result {unable to convert date-time string*} +test clock-34.56 {clock scan, ISO 8601 invalid TZ} -body { + set time [clock scan "19921023M000000"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} -returnCodes error -match glob -result {unable to convert date-time string*} +test clock-34.57 {clock scan, ISO 8601 invalid TZ} -body { + set time [clock scan "1992-10-23M00:00:00"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} -returnCodes error -match glob -result {unable to convert date-time string*} +test clock-34.58 {clock scan, ISO 8601 invalid TZ} -body { + set time [clock scan "1992-10-23MST00:00:00"] + clock format $time -format {%b %d, %Y %H:%M:%S} +} -returnCodes error -match glob -result {unable to convert date-time string*} +test clock-34.59 {clock scan tests (-TZ)} { + set time [clock scan "31 Jan 14 23:59:59 -0100"] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Feb 01,2014 00:59:59 GMT} +test clock-34.60 {clock scan tests (+TZ)} { + set time [clock scan "31 Jan 14 23:59:59 +0100"] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 31,2014 22:59:59 GMT} +test clock-34.61 {clock scan tests (-TZ)} { + set time [clock scan "23:59:59 -0100" -base 0 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 02,1970 00:59:59 GMT} +test clock-34.62 {clock scan tests (+TZ)} { + set time [clock scan "23:59:59 +0100" -base 0 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 01,1970 22:59:59 GMT} +test clock-34.63 {clock scan tests (TZ)} { + set time [clock scan "Mon, 30 Jun 2014 23:59:59 CEST"] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jun 30,2014 21:59:59 GMT} +test clock-34.64 {clock scan tests (TZ)} { + set time [clock scan "Fri, 31 Jan 2014 23:59:59 CET"] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 31,2014 22:59:59 GMT} +test clock-34.65 {clock scan tests (relspec, day unit not TZ)} { + set time [clock scan "23:59:59 +15 day" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Feb 08,1970 23:59:59 GMT} +test clock-34.66 {clock scan tests (relspec, day unit not TZ)} { + set time [clock scan "23:59:59 -15 day" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 09,1970 23:59:59 GMT} +test clock-34.67 {clock scan tests (merid and TZ)} { + set time [clock scan "10:59 pm CET" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 24,1970 21:59:00 GMT} +test clock-34.68 {clock scan tests (merid and TZ)} { + set time [clock scan "10:59 pm +0100" -base 2000000 -gmt true] + clock format $time -format {%b %d,%Y %H:%M:%S %Z} -gmt true +} {Jan 24,1970 21:59:00 GMT} -test clock-34.53.1 {relative from base, date switch} { +test clock-34.69.1 {relative from base, date switch} { set base [clock scan "12/31/2016 23:59:59" -gmt 1] clock format [clock scan "+1 second" \ -base $base -gmt 1] -gmt 1 -format {%Y-%m-%d %H:%M:%S} } {2017-01-01 00:00:00} - -test clock-34.53.2 {relative time, daylight switch} { +test clock-34.69.2 {relative time, daylight switch} { set base [clock scan "03/27/2016" -timezone CET] set res {} lappend res [clock format [clock scan "+1 hour" \ @@ -36304,7 +36732,7 @@ test clock-34.53.2 {relative time, daylight switch} { -base $base -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] } {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} -test clock-34.53.3 {relative time with day increment / daylight switch} { +test clock-34.69.3 {relative time with day increment / daylight switch} { set base [clock scan "03/27/2016" -timezone CET] set res {} lappend res [clock format [clock scan "+5 day +25 hour" \ @@ -36313,7 +36741,7 @@ test clock-34.53.3 {relative time with day increment / daylight switch} { -base [expr {$base - 6*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] } {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} -test clock-34.53.4 {relative time with month & day increment / daylight switch} { +test clock-34.69.4 {relative time with month & day increment / daylight switch} { set base [clock scan "03/27/2016" -timezone CET] set res {} lappend res [clock format [clock scan "next Mar +5 day +25 hour" \ @@ -36322,7 +36750,7 @@ test clock-34.53.4 {relative time with month & day increment / daylight switch} -base [expr {$base - 35*24*60*60}] -timezone CET] -timezone CET -format {%Y-%m-%d %H:%M:%S %Z}] } {{2016-03-27 01:00:00 CET} {2016-03-27 03:00:00 CEST}} -test clock-34.54.1 {check date in DST-hole: daylight switch CET -> CEST} { +test clock-34.70.1 {check date in DST-hole: daylight switch CET -> CEST} { set res {} # forwards set base 1459033200 @@ -36350,7 +36778,7 @@ test clock-34.54.1 {check date in DST-hole: daylight switch CET -> CEST} { 1459033200 = 2016-03-27 00:00:00 CET } {}] \n] -test clock-34.54.2 {check date in DST-hole: daylight switch CEST -> CET} { +test clock-34.70.2 {check date in DST-hole: daylight switch CEST -> CET} { set res {} # forwards set base 1477782000 @@ -36380,7 +36808,7 @@ test clock-34.54.2 {check date in DST-hole: daylight switch CEST -> CET} { # clock seconds test clock-35.1 {clock seconds tests} { - expr [clock seconds]+1 + expr {[clock seconds] + 1} concat {} } {} test clock-35.2 {clock seconds tests} { @@ -36390,7 +36818,7 @@ test clock-35.3 {clock seconds tests} { set start [clock seconds] after 2000 set end [clock seconds] - expr "$end > $start" + expr {$end > $start} } {1} @@ -36488,6 +36916,52 @@ test clock-38.2 {make sure TZ is not cached after unset} \ } \ -result 1 +test clock-38.3sc {ensure cache of base is correct for :localtime if TZ-env changing / scan} \ + -setup { + if { [info exists env(TZ)] } { + set oldTZ $env(TZ) + } + } \ + -body { + set res {} + foreach env(TZ) {GMT-11:30 GMT-07:30 GMT-03:30 GMT} \ + i {{07:30:00} {03:30:00} {23:30:00} {20:00:00}} \ + { + lappend res [clock scan $i -format "%H:%M:%S" -base [expr {20*60*60}] -timezone :localtime] + } + set res + } \ + -cleanup { + if { [info exists oldTZ] } { + set env(TZ) $oldTZ + unset oldTZ + } else { + unset env(TZ) + } + } \ + -result [lrepeat 4 [expr {20*60*60}]] +test clock-38.3fm {ensure cache of base is correct for :localtime if TZ-env changing / format} \ + -setup { + if { [info exists env(TZ)] } { + set oldTZ $env(TZ) + } + } \ + -body { + set res {} + foreach env(TZ) {GMT-11:30 GMT-07:30 GMT-03:30 GMT} { + lappend res [clock format [expr {20*60*60}] -format "%Y-%m-%dT%H:%M:%S %Z" -timezone :localtime] + } + set res + } \ + -cleanup { + if { [info exists oldTZ] } { + set env(TZ) $oldTZ + unset oldTZ + } else { + unset env(TZ) + } + } \ + -result {{1970-01-02T07:30:00 +1130} {1970-01-02T03:30:00 +0730} {1970-01-01T23:30:00 +0330} {1970-01-01T20:00:00 +0000}} test clock-39.1 {regression - synonym timezones} { clock format 0 -format {%H:%M:%S} -timezone :US/Eastern @@ -36538,7 +37012,7 @@ test clock-42.1 {regression test - %z in :localtime when west of Greenwich } \ } \ -result {-0500} -# 43.1 was a bad test - mktime returning -1 is an error according to posix. +# 43.1 was a bad test - mktime returning -1 is an error according to Posix. test clock-44.1 {regression test - time zone name containing hyphen } \ -setup { @@ -36565,6 +37039,19 @@ test clock-44.2 {regression test - time zone containing only two digits} \ } \ -result 482134530 +test clock-44.3 {regression test - spaces between some scan tokens are optional (TCL_CLOCK_FULL_COMPAT, no-strict only)} \ + -body { + list [clock scan {9 Apr 2024} -format {%d %b%Y} -gmt 1] \ + [clock scan {Tue, 9 Apr 2024 00:00:00 +0000} -format {%a, %d %b%Y %H:%M:%S %Z} -gmt 1] + } \ + -result {1712620800 1712620800} +test clock-44.4 {regression test - spaces between all scan tokens are optional (TCL_CLOCK_FULL_COMPAT, no-strict only)} \ + -body { + list [clock scan {9 Apr 2024} -format {%d%b%Y} -gmt 1] \ + [clock scan {Tue, 9 Apr 2024 00:00:00 +0000} -format {%a,%d%b%Y%H:%M:%S%Z} -gmt 1] + } \ + -result {1712620800 1712620800} + test clock-45.1 {compat: scan regression on spaces (multiple spaces in format)} \ -body { list \ @@ -36606,6 +37093,28 @@ test clock-45.4 {compat: scan regression on spaces (mandatory leading/trailing s [catch {clock scan "11 1 120" -format " %y%m%d %H%M%S" -gmt 1} ret] $ret \ [catch {clock scan "11 1 120" -format " %y%m%d %H%M%S " -gmt 1} ret] $ret } -result [lrepeat 3 1 "input string does not match supplied format"] +test clock-45.5 {regression test - freescan no int overflow} { + # note that the relative date changes currently reset the time to 00:00, + # this can be changed later (simply achievable by adding 00:00 if expected): + list \ + [clock scan "+24856 days" -base 1600000000 -gmt 1] \ + [clock scan "+815 months" -base 1600000000 -gmt 1] \ + [clock scan "+69 years" -base 1600000000 -gmt 1] \ + [clock scan "+596524 hours" -base 1600000000 -gmt 1] \ + [clock scan "+35791395 minutes" -base 1600000000 -gmt 1] \ + [clock scan "+2147483647 seconds" -base 1600000000 -gmt 1] +} {3747513600 3743193600 3777408000 3747486400 3747483700 3747483647} +test clock-45.6 {regression test - freescan no int overflow} { + # note that the relative date changes currently reset the time to 00:00, + # this can be changed later (simply achievable by adding 00:00 if expected): + list \ + [clock scan "-24856 days" -base 2177452800 -gmt 1] \ + [clock scan "-815 months" -base 2177452800 -gmt 1] \ + [clock scan "-69 years" -base 2177452800 -gmt 1] \ + [clock scan "-596524 hours" -base 2177452800 -gmt 1] \ + [clock scan "-35791395 minutes" -base 2177452800 -gmt 1] \ + [clock scan "-2147483647 seconds" -base 2177452800 -gmt 1] +} {29894400 34214400 0 29966400 29969100 29969153} test clock-46.1 {regression test - month zero} -constraints valid_off \ -body { @@ -36635,62 +37144,126 @@ test clock-46.6 {freescan: regression test - bad time} -constraints valid_off \ # 13:00 am/pm are invalid input strings... list [clock scan "13:00 am" -base 0 -gmt 1] \ [clock scan "13:00 pm" -base 0 -gmt 1] - } -result {-1 -1} + } -result {3600 46800} + +if {!$valid_mode} { + test clock-46.7a {regression test - switch day by large not-valid time, see bug [3ee8f1c2a785f4d8]} {valid_off} { + list [clock scan 23:59:59 -base 0 -gmt 1 -format %H:%M:%S] \ + [clock scan 24:00:00 -base 0 -gmt 1 -format %H:%M:%S] \ + [clock scan 48:00:00 -base 0 -gmt 1 -format %H:%M:%S] + } {86399 86400 172800} + test clock-46.7b {freescan: regression test - switch day by large not-valid time, see bug [3ee8f1c2a785f4d8]} {valid_off} { + list [clock scan 23:59:59 -base 0 -gmt 1] \ + [clock scan 24:00:00 -base 0 -gmt 1] \ + [clock scan 48:00:00 -base 0 -gmt 1] + } {86399 86400 172800} +} else { + test clock-46.8a {regression test - invalid time (hour)} { + list [catch {clock scan 24:00:00 -base 0 -gmt 1 -format %H:%M:%S} msg] $msg \ + [catch {clock scan 48:00:00 -base 0 -gmt 1 -format %H:%M:%S} msg] $msg + } {1 {unable to convert input string: invalid time (hour)} 1 {unable to convert input string: invalid time (hour)}} + test clock-46.8b {freescan: regression test - invalid time (hour)} { + list [catch {clock scan 24:00:00 -base 0 -gmt 1} msg] $msg \ + [catch {clock scan 48:00:00 -base 0 -gmt 1} msg] $msg + } {1 {unable to convert input string: invalid time (hour)} 1 {unable to convert input string: invalid time (hour)}} +} +proc _invalid_test {testtz scnargs args} { + global valid_mode + # ensure validation works TZ independently, since the conversion + # of local time to UTC may adjust date/time tokens, depending on TZ: + set res {} + if {$testtz eq ""} { + set testtz {:GMT :CET {} :Europe/Berlin :localtime} + } + if {!$valid_mode} { # globally -valid 0, so add it explicitely + lappend scnargs -valid 1 + } + foreach tz $testtz { + foreach {v} $args { + lappend res [catch {clock scan $v {*}$scnargs -timezone $tz} msg] $msg + } + } + set res +} # test without and with relative offsets: foreach {idx relstr} {"" "" "+rel" "+ 15 month + 40 days + 30 hours + 80 minutes +9999 seconds"} { test clock-46.10$idx {freescan: validation rules: invalid time} \ -body { # 13:00 am/pm are invalid input strings... - list [catch {clock scan "13:00 am$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "13:00 pm$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time (hour)} 1 {unable to convert input string: invalid time (hour)}} + _invalid_test {} {} "13:00 am$relstr" "13:00 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (hour)}] test clock-46.11$idx {freescan: validation rules: invalid time} \ -body { # invalid minutes in input strings... - list [catch {clock scan "23:70$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "11:80 pm$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time (minutes)} 1 {unable to convert input string: invalid time (minutes)}} + _invalid_test {} {} "23:70$relstr" "11:80 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (minutes)}] test clock-46.12$idx {freescan: validation rules: invalid time} \ -body { # invalid seconds in input strings... - list [catch {clock scan "23:00:70$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "11:00:80 pm$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time} 1 {unable to convert input string: invalid time}} + _invalid_test {} {} "23:00:70$relstr" "11:00:80 pm$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid time}] test clock-46.13$idx {freescan: validation rules: invalid day} \ -body { - list [catch {clock scan "29 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "30 Feb 2016$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} + _invalid_test {} {} "29 Feb 2017$relstr" "30 Feb 2016$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] test clock-46.14$idx {freescan: validation rules: invalid day} \ -body { - list [catch {clock scan "0 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "00 Feb 2017$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} + _invalid_test {} {} "0 Feb 2017$relstr" "00 Feb 2017$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] test clock-46.15$idx {freescan: validation rules: invalid month} \ -body { - list [catch {clock scan "13/13/2017$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "00/00/2017$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid month} 1 {unable to convert input string: invalid month}} + _invalid_test {} {} "13/13/2017$relstr" "00/00/2017$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid month}] test clock-46.16$idx {freescan: validation rules: invalid day of week} \ -body { - list [catch {clock scan "Sat Jan 01 00:00:00 1970$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "Thu Jan 03 00:00:00 1970$relstr" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day of week} 1 {unable to convert input string: invalid day of week}} + _invalid_test {} {} "Sat Jan 02 00:00:00 1970$relstr" "Thu Jan 04 00:00:00 1970$relstr" + } -result [lrepeat 10 1 {unable to convert input string: invalid day of week}] test clock-46.17$idx {scan: validation rules: invalid year} -setup { set orgcfg [list -min-year [clock configure -min-year] -max-year [clock configure -max-year] \ -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 } -body { - list [catch {clock scan "70-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "1870-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "9570-01-01$relstr" -valid 1 -gmt 1} msg] $msg \ - } -result [lrepeat 3 1 {unable to convert input string: invalid year}] -cleanup { + _invalid_test {} {} "70-01-01$relstr" "1870-01-01$relstr" "9570-01-01$relstr" + } -result [lrepeat 15 1 {unable to convert input string: invalid year}] -cleanup { clock configure {*}$orgcfg unset -nocomplain orgcfg } }; # foreach +test clock-46.16-pos-fs {freescan: validation rules: valid day of week (must work for all weekdays)} \ + -body { + _invalid_test {:GMT -12:00 +12:00} {} {Sat, 01 Jan 2000 00:00:00} {Sun, 02 Jan 2000 00:00:00} {Mon, 03 Jan 2000 00:00:00} {Tue, 04 Jan 2000 00:00:00} {Wed, 05 Jan 2000 00:00:00} {Thu, 06 Jan 2000 00:00:00} {Fri, 07 Jan 2000 00:00:00} + } -result [list \ + 0 946684800 0 946771200 0 946857600 0 946944000 0 947030400 0 947116800 0 947203200 \ + 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 0 947246400 \ + 0 946641600 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 \ + ] +test clock-46.16-pos-fmt1 {scan with format: validation rules: valid day of week (must work for all weekdays)} \ + -body { + _invalid_test {:GMT -12:00 +12:00} {-format "%a, %d %b %Y %H:%M:%S"} {Sat, 01 Jan 2000 00:00:00} {Sun, 02 Jan 2000 00:00:00} {Mon, 03 Jan 2000 00:00:00} {Tue, 04 Jan 2000 00:00:00} {Wed, 05 Jan 2000 00:00:00} {Thu, 06 Jan 2000 00:00:00} {Fri, 07 Jan 2000 00:00:00} + } -result [list \ + 0 946684800 0 946771200 0 946857600 0 946944000 0 947030400 0 947116800 0 947203200 \ + 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 0 947246400 \ + 0 946641600 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 \ + ] +test clock-46.16-pos-fmt2 {scan with format: validation rules: valid day of week (must work for all weekdays)} \ + -body { + _invalid_test {:GMT -12:00 +12:00} {-format "%u, %d %b %Y %H:%M:%S"} {6, 01 Jan 2000 00:00:00} {7, 02 Jan 2000 00:00:00} {1, 03 Jan 2000 00:00:00} {2, 04 Jan 2000 00:00:00} {3, 05 Jan 2000 00:00:00} {4, 06 Jan 2000 00:00:00} {5, 07 Jan 2000 00:00:00} + } -result [list \ + 0 946684800 0 946771200 0 946857600 0 946944000 0 947030400 0 947116800 0 947203200 \ + 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 0 947246400 \ + 0 946641600 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 \ + ] +test clock-46.16-pos-fmt2 {scan with format: validation rules: valid day of week (must work for all weekdays)} \ + -body { + _invalid_test {:GMT -12:00 +12:00} {-format "%w, %d %b %Y %H:%M:%S"} {6, 01 Jan 2000 00:00:00} {0, 02 Jan 2000 00:00:00} {1, 03 Jan 2000 00:00:00} {2, 04 Jan 2000 00:00:00} {3, 05 Jan 2000 00:00:00} {4, 06 Jan 2000 00:00:00} {5, 07 Jan 2000 00:00:00} + } -result [list \ + 0 946684800 0 946771200 0 946857600 0 946944000 0 947030400 0 947116800 0 947203200 \ + 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 0 947246400 \ + 0 946641600 0 946728000 0 946814400 0 946900800 0 946987200 0 947073600 0 947160000 \ + ] +rename _invalid_test {} unset -nocomplain idx relstr set dst_hole_check { @@ -36755,76 +37328,80 @@ test clock-46.19-4 {scan: validation rules regression: all scans successful, if } -result [lrepeat 4 {*}[if {$valid_mode} {list 0 1 1 0 0 0} else {list 0 0 0 0 0 0}]] unset -nocomplain dst_hole_check +proc _invalid_test {args} { + global valid_mode + # ensure validation works TZ independently, since the conversion + # of local time to UTC may adjust date/time tokens, depending on TZ: + set res {} + foreach tz {:GMT :CET {} :Europe/Berlin :localtime} { + foreach {v fmt} $args { + if {$valid_mode} { # globally -valid 1 + lappend res [catch {clock scan $v -format $fmt -timezone $tz} msg] $msg + } else { + lappend res [catch {clock scan $v -format $fmt -valid 1 -timezone $tz} msg] $msg + } + + } + } + set res +} test clock-46.20 {scan: validation rules: invalid time} \ -body { # 13:00 am/pm are invalid input strings... - list [catch {clock scan "13:00 am" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "13:00 pm" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time (hour)} 1 {unable to convert input string: invalid time (hour)}} + _invalid_test "13:00 am" "%H:%M %p" "13:00 pm" "%H:%M %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (hour)}] test clock-46.21 {scan: validation rules: invalid time} \ -body { # invalid minutes in input strings... - list [catch {clock scan "23:70" -format "%H:%M" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "11:80 pm" -format "%H:%M %p" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time (minutes)} 1 {unable to convert input string: invalid time (minutes)}} + _invalid_test "23:70" "%H:%M" "11:80 pm" "%H:%M %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time (minutes)}] test clock-46.22 {scan: validation rules: invalid time} \ -body { # invalid seconds in input strings... - list [catch {clock scan "23:00:70" -format "%H:%M:%S" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "11:00:80 pm" -format "%H:%M:%S %p" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid time} 1 {unable to convert input string: invalid time}} + _invalid_test "23:00:70" "%H:%M:%S" "11:00:80 pm" "%H:%M:%S %p" + } -result [lrepeat 10 1 {unable to convert input string: invalid time}] test clock-46.23 {scan: validation rules: invalid day} \ -body { - list [catch {clock scan "29 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "30 Feb 2016" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} + _invalid_test "29 Feb 2017" "%d %b %Y" "30 Feb 2016" "%d %b %Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] test clock-46.24 {scan: validation rules: invalid day} \ -body { - list [catch {clock scan "0 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "00 Feb 2017" -format "%d %b %Y" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day} 1 {unable to convert input string: invalid day}} + _invalid_test "0 Feb 2017" "%d %b %Y" "00 Feb 2017" "%d %b %Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid day}] test clock-46.25 {scan: validation rules: invalid month} \ -body { - list [catch {clock scan "13/13/2017" -format "%m/%d/%Y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "00/00/2017" -format "%m/%d/%Y" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid month} 1 {unable to convert input string: invalid month}} + _invalid_test "13/13/2017" "%m/%d/%Y" "00/01/2017" "%m/%d/%Y" + } -result [lrepeat 10 1 {unable to convert input string: invalid month}] test clock-46.26 {scan: validation rules: ambiguous day} \ -body { - list [catch {clock scan "1970-01-01--002" -format "%Y-%m-%d--%j" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "70-01-01--002" -format "%y-%m-%d--%j" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: ambiguous day} 1 {unable to convert input string: ambiguous day}} + _invalid_test "1970-01-02--004" "%Y-%m-%d--%j" "70-01-02--004" "%y-%m-%d--%j" + } -result [lrepeat 10 1 {unable to convert input string: ambiguous day}] test clock-46.27 {scan: validation rules: ambiguous year} \ -body { - list [catch {clock scan "19700101 00W014" -format "%Y%m%d %gW%V%u" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "1970001 00W014" -format "%Y%j %gW%V%u" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: ambiguous year} 1 {unable to convert input string: ambiguous year}} + _invalid_test "19700106 00W014" "%Y%m%d %gW%V%u" "1970006 00W014" "%Y%j %gW%V%u" + } -result [lrepeat 10 1 {unable to convert input string: ambiguous year}] test clock-46.28 {scan: validation rules: invalid day of week} \ -body { - list [catch {clock scan "Sat Jan 01 00:00:00 1970" -format "%a %b %d %H:%M:%S %Y" -valid 1 -gmt 1} msg] $msg - } -result {1 {unable to convert input string: invalid day of week}} + _invalid_test "Sat Jan 02 00:00:00 1970" "%a %b %d %H:%M:%S %Y" + } -result [lrepeat 5 1 {unable to convert input string: invalid day of week}] test clock-46.29-1 {scan: validation rules: invalid day of year} \ -body { - list [catch {clock scan "000-2017" -format "%j-%Y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "366-2017" -format "%j-%Y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "000-2017" -format "%j-%G" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "366-2017" -format "%j-%G" -valid 1 -gmt 1} msg] $msg - } -result [lrepeat 4 1 {unable to convert input string: invalid day of year}] + _invalid_test "000-2017" "%j-%Y" "366-2017" "%j-%Y" "000-2017" "%j-%G" "366-2017" "%j-%G" + } -result [lrepeat 20 1 {unable to convert input string: invalid day of year}] test clock-46.29-2 {scan: validation rules: valid day of leap/not leap year} \ -body { - list [clock format [clock scan "366-2016" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ - [clock format [clock scan "365-2017" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ - [clock format [clock scan "366-2016" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y"] \ - [clock format [clock scan "365-2017" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y"] + list [clock format [clock scan "366-2016" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y" -gmt 1] \ + [clock format [clock scan "365-2017" -format "%j-%Y" -valid 1 -gmt 1] -format "%d-%m-%Y" -gmt 1] \ + [clock format [clock scan "366-2016" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y" -gmt 1] \ + [clock format [clock scan "365-2017" -format "%j-%G" -valid 1 -gmt 1] -format "%d-%m-%Y" -gmt 1] } -result {31-12-2016 31-12-2017 31-12-2016 31-12-2017} test clock-46.30 {scan: validation rules: invalid year} -setup { set orgcfg [list -min-year [clock configure -min-year] -max-year [clock configure -max-year] \ -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 } -body { - list [catch {clock scan "01-01-70" -format "%d-%m-%y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "01-01-1870" -format "%d-%m-%C%y" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "01-01-1970" -format "%d-%m-%Y" -valid 1 -gmt 1} msg] $msg \ - } -result [lrepeat 3 1 {unable to convert input string: invalid year}] -cleanup { + _invalid_test "01-01-70" "%d-%m-%y" "01-01-1870" "%d-%m-%C%y" "01-01-1970" "%d-%m-%Y" + } -result [lrepeat 15 1 {unable to convert input string: invalid year}] -cleanup { clock configure {*}$orgcfg unset -nocomplain orgcfg } @@ -36833,13 +37410,12 @@ test clock-46.31 {scan: validation rules: invalid iso year} -setup { -year-century [clock configure -year-century] -century-switch [clock configure -century-switch]] clock configure -min-year 2000 -max-year 2100 -year-century 2000 -century-switch 38 } -body { - list [catch {clock scan "01-01-70" -format "%d-%m-%g" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "01-01-9870" -format "%d-%m-%C%g" -valid 1 -gmt 1} msg] $msg \ - [catch {clock scan "01-01-9870" -format "%d-%m-%G" -valid 1 -gmt 1} msg] $msg \ - } -result [lrepeat 3 1 {unable to convert input string: invalid iso year}] -cleanup { + _invalid_test "01-01-70" "%d-%m-%g" "01-01-9870" "%d-%m-%C%g" "01-01-9870" "%d-%m-%G" + } -result [lrepeat 15 1 {unable to convert input string: invalid iso year}] -cleanup { clock configure {*}$orgcfg unset -nocomplain orgcfg } +rename _invalid_test {} test clock-47.1 {regression test - four-digit time} { clock scan 0012 @@ -37521,6 +38097,14 @@ test clock-57.1 {clock scan - abbreviated options} { clock scan 1970-01-01 -f %Y-%m-%d -g true } 0 +test clock-57.2 {clock scan - not -gmt and -timezone in the same call} { + catch {clock scan 1970-01-01 -format %Y-%m-%d -gmt true -timezone :Europe/Berlin} +} 1 + +test clock-57.3 {clock scan - not -g and -timezone in the same call} { + catch {clock scan 1970-01-01 -format %Y-%m-%d -g true -timezone :Europe/Berlin} +} 1 + test clock-58.1 {clock l10n - Japanese localisation} {*}{ -setup { proc backslashify { string } { @@ -37626,6 +38210,26 @@ test clock-59.1 {military time zones} { join $trouble \n } {} +test clock-59.2 {correct time zone names, format and scan back, bug and regression [2c237beffbace823]} { + set result {} + foreach {base tz} { + 1700000000 "Etc/GMT-2" + 1700000000 "Etc/GMT+2" + 1301184000 "Europe/Kaliningrad" + 152632800 "Pacific/Chatham" + -765317964 "America/Paramaribo" + -2337928528 "Australia/Eucla" + } { + set v [clock format $base -timezone Etc/GMT-2 -format "%Y-%m-%d %H:%M:%S %Z"] + lappend result [expr {[set t [clock scan $v -format "%Y-%m-%d %H:%M:%S %Z"]] == $base ? 1 : "0 ($t != $base for $v)"}] + # free-scan only for 8.6.16+ (tzdata called +0200 instead of +02 for Etc/GMT-2) to avoid confusion: + if {[package vsatisfies [package provide Tcl] 8.6.16]} { + lappend result [expr {[set t [clock scan $v]] == $base ? 1 : "0 ($t != $base for $v)"}] + } + } + set result +} [if {[package vsatisfies [package provide Tcl] 8.6.16]} {lrepeat 12 1} else {lrepeat 6 1}] + # case-insensitive matching of weekday and month names [Bug 1781282] test clock-60.1 {case insensitive weekday names} { @@ -37738,6 +38342,15 @@ test clock-65.1 {clock add, bad option [Bug 2481670]} {*}{ -result {bad option "-foo"*} } +test clock-65.2 {clock add with both -timezone and -gmt} {*}{ + -body { + clock add 0 1 year -timezone :CET -gmt true + } + -match glob + -returnCodes error + -result {cannot use -gmt and -timezone in same call} +} + test clock-66.1 {clock scan, no date, never-before-seen timezone} {*}{ -setup { ::tcl::clock::ClearCaches @@ -37759,12 +38372,10 @@ test clock-67.2 {Bug d19a30db57} -body { # error, not segfault tcl::clock::GetJulianDayFromEraYearMonthDay {} 2361222 } -returnCodes error -match glob -result * - test clock-67.3 {Bug d19a30db57} -body { # error, not segfault tcl::clock::GetJulianDayFromEraYearWeekDay {} 2361222 } -returnCodes error -match glob -result * - test clock-67.4 {Change format %x output on global locale change [Bug 4a0c163d24]} -setup { package require msgcat set current [msgcat::mclocale] @@ -37776,7 +38387,6 @@ test clock-67.4 {Change format %x output on global locale change [Bug 4a0c163d24 } -cleanup { msgcat::mclocale $current } -result {1 1} - test clock-67.5 {Change scan %x output on global locale change [Bug 4a0c163d24]} -setup { package require msgcat set current [msgcat::mclocale] @@ -37794,9 +38404,16 @@ test clock-67.5 {Change scan %x output on global locale change [Bug 4a0c163d24]} ::tcl::clock::ClearCaches rename test {} -rename __test test +namespace import -force ::tcltest::* +# adjust expected skipped (valid_off is an artificial constraint): +if {$valid_mode && [info exists ::tcltest::skippedBecause(valid_off)]} { + incr ::tcltest::numTests(Total) -$::tcltest::skippedBecause(valid_off) + incr ::tcltest::numTests(Skipped) -$::tcltest::skippedBecause(valid_off) + unset ::tcltest::skippedBecause(valid_off) +} ::tcltest::cleanupTests namespace delete ::testClock +unset valid_mode return