diff options
| -rw-r--r-- | lisp/time-stamp.el | 241 | ||||
| -rw-r--r-- | test/lisp/time-stamp-tests.el | 414 |
2 files changed, 624 insertions, 31 deletions
diff --git a/lisp/time-stamp.el b/lisp/time-stamp.el index 0cc566f0d8c..ae911717151 100644 --- a/lisp/time-stamp.el +++ b/lisp/time-stamp.el | |||
| @@ -25,7 +25,7 @@ | |||
| 25 | 25 | ||
| 26 | ;; A template in a file can be updated with a new time stamp when | 26 | ;; A template in a file can be updated with a new time stamp when |
| 27 | ;; you save the file. For example: | 27 | ;; you save the file. For example: |
| 28 | ;; static char *ts = "sdmain.c Time-stamp: <2001-08-13 10:20:51 gildea>"; | 28 | ;; static char *ts = "sdmain.c Time-stamp: <2020-04-18 14:10:21 gildea>"; |
| 29 | 29 | ||
| 30 | ;; To use time-stamping, add this line to your init file: | 30 | ;; To use time-stamping, add this line to your init file: |
| 31 | ;; (add-hook 'before-save-hook 'time-stamp) | 31 | ;; (add-hook 'before-save-hook 'time-stamp) |
| @@ -278,7 +278,7 @@ look like one of the following: | |||
| 278 | Time-stamp: <> | 278 | Time-stamp: <> |
| 279 | Time-stamp: \" \" | 279 | Time-stamp: \" \" |
| 280 | The time stamp is written between the brackets or quotes: | 280 | The time stamp is written between the brackets or quotes: |
| 281 | Time-stamp: <2001-02-18 10:20:51 gildea> | 281 | Time-stamp: <2020-08-07 17:10:21 gildea> |
| 282 | 282 | ||
| 283 | The time stamp is updated only if the variable | 283 | The time stamp is updated only if the variable |
| 284 | `time-stamp-active' is non-nil. | 284 | `time-stamp-active' is non-nil. |
| @@ -422,7 +422,7 @@ Returns the end point, which is where `time-stamp' begins the next search." | |||
| 422 | ;;;###autoload | 422 | ;;;###autoload |
| 423 | (defun time-stamp-toggle-active (&optional arg) | 423 | (defun time-stamp-toggle-active (&optional arg) |
| 424 | "Toggle `time-stamp-active', setting whether \\[time-stamp] updates a buffer. | 424 | "Toggle `time-stamp-active', setting whether \\[time-stamp] updates a buffer. |
| 425 | With ARG, turn time stamping on if and only if arg is positive." | 425 | With ARG, turn time stamping on if and only if ARG is positive." |
| 426 | (interactive "P") | 426 | (interactive "P") |
| 427 | (setq time-stamp-active | 427 | (setq time-stamp-active |
| 428 | (if (null arg) | 428 | (if (null arg) |
| @@ -457,7 +457,7 @@ normally the current time is used." | |||
| 457 | (defun time-stamp-string-preprocess (format &optional time) | 457 | (defun time-stamp-string-preprocess (format &optional time) |
| 458 | "Use a FORMAT to format date, time, file, and user information. | 458 | "Use a FORMAT to format date, time, file, and user information. |
| 459 | Optional second argument TIME is only for testing. | 459 | Optional second argument TIME is only for testing. |
| 460 | Implements non-time extensions to `format-time-string' | 460 | Implements extensions to `format-time-string' |
| 461 | and all `time-stamp-format' compatibility." | 461 | and all `time-stamp-format' compatibility." |
| 462 | (let ((fmt-len (length format)) | 462 | (let ((fmt-len (length format)) |
| 463 | (ind 0) | 463 | (ind 0) |
| @@ -477,6 +477,9 @@ and all `time-stamp-format' compatibility." | |||
| 477 | (alt-form 0) | 477 | (alt-form 0) |
| 478 | (change-case nil) | 478 | (change-case nil) |
| 479 | (upcase nil) | 479 | (upcase nil) |
| 480 | (flag-pad-with-spaces nil) | ||
| 481 | (flag-pad-with-zeros nil) | ||
| 482 | (flag-minimize nil) | ||
| 480 | (paren-level 0)) | 483 | (paren-level 0)) |
| 481 | ;; eat any additional args to allow for future expansion | 484 | ;; eat any additional args to allow for future expansion |
| 482 | (while (progn | 485 | (while (progn |
| @@ -521,10 +524,12 @@ and all `time-stamp-format' compatibility." | |||
| 521 | (setq change-case t)) | 524 | (setq change-case t)) |
| 522 | ((eq cur-char ?^) | 525 | ((eq cur-char ?^) |
| 523 | (setq upcase t)) | 526 | (setq upcase t)) |
| 527 | ((eq cur-char ?0) | ||
| 528 | (setq flag-pad-with-zeros t)) | ||
| 524 | ((eq cur-char ?-) | 529 | ((eq cur-char ?-) |
| 525 | (setq field-width "1")) | 530 | (setq field-width "1" flag-minimize t)) |
| 526 | ((eq cur-char ?_) | 531 | ((eq cur-char ?_) |
| 527 | (setq field-width "2")))) | 532 | (setq field-width "2" flag-pad-with-spaces t)))) |
| 528 | (setq field-result | 533 | (setq field-result |
| 529 | (cond | 534 | (cond |
| 530 | ((eq cur-char ?%) | 535 | ((eq cur-char ?%) |
| @@ -586,26 +591,37 @@ and all `time-stamp-format' compatibility." | |||
| 586 | ((eq cur-char ?Y) ;4-digit year | 591 | ((eq cur-char ?Y) ;4-digit year |
| 587 | (string-to-number (time-stamp--format "%Y" time))) | 592 | (string-to-number (time-stamp--format "%Y" time))) |
| 588 | ((eq cur-char ?z) ;time zone offset | 593 | ((eq cur-char ?z) ;time zone offset |
| 589 | (if change-case | 594 | (let ((field-width-num (string-to-number field-width)) |
| 590 | "" ;discourage %z variations | 595 | ;; Handle numeric time zone ourselves, because |
| 591 | (cond ((= alt-form 0) | 596 | ;; current-time-zone cannot handle offsets |
| 592 | (if (string-equal field-width "") | 597 | ;; greater than 24 hours. |
| 593 | (progn | 598 | (offset-secs |
| 594 | (time-stamp-conv-warn "%z" "%#Z") | 599 | (cond ((numberp time-stamp-time-zone) |
| 595 | (time-stamp--format "%#Z" time)) | 600 | time-stamp-time-zone) |
| 596 | (cond ((string-equal field-width "1") | 601 | ((and (consp time-stamp-time-zone) |
| 597 | (setq field-width "3")) ;%-z -> "+00" | 602 | (numberp (car time-stamp-time-zone))) |
| 598 | ((string-equal field-width "2") | 603 | (car time-stamp-time-zone)) |
| 599 | (setq field-width "5")) ;%_z -> "+0000" | 604 | ;; interpret text time zone |
| 600 | ((string-equal field-width "4") | 605 | (t (car (current-time-zone |
| 601 | (setq field-width "0"))) ;discourage %4z | 606 | time time-stamp-time-zone)))))) |
| 602 | (time-stamp--format "%z" time))) | 607 | ;; we do our own padding; do not let it be updated further |
| 603 | ((= alt-form 1) | 608 | (setq field-width "") |
| 604 | (time-stamp--format "%:z" time)) | 609 | (cond (change-case |
| 605 | ((= alt-form 2) | 610 | "") ;discourage %z variations |
| 606 | (time-stamp--format "%::z" time)) | 611 | ((and (= alt-form 0) |
| 607 | ((= alt-form 3) | 612 | (not flag-minimize) |
| 608 | (time-stamp--format "%:::z" time))))) | 613 | (not flag-pad-with-spaces) |
| 614 | (not flag-pad-with-zeros) | ||
| 615 | (= field-width-num 0)) | ||
| 616 | (time-stamp-conv-warn "%z" "%#Z") | ||
| 617 | (time-stamp--format "%#Z" time)) | ||
| 618 | (t (time-stamp-formatz-from-parsed-options | ||
| 619 | flag-minimize | ||
| 620 | flag-pad-with-spaces | ||
| 621 | flag-pad-with-zeros | ||
| 622 | alt-form | ||
| 623 | field-width-num | ||
| 624 | offset-secs))))) | ||
| 609 | ((eq cur-char ?Z) ;time zone name | 625 | ((eq cur-char ?Z) ;time zone name |
| 610 | (if change-case | 626 | (if change-case |
| 611 | (time-stamp--format "%#Z" time) | 627 | (time-stamp--format "%#Z" time) |
| @@ -653,7 +669,8 @@ and all `time-stamp-format' compatibility." | |||
| 653 | (string-to-number field-width)))) | 669 | (string-to-number field-width)))) |
| 654 | (if (> initial-length desired-length) | 670 | (if (> initial-length desired-length) |
| 655 | ;; truncate strings on right | 671 | ;; truncate strings on right |
| 656 | (if (stringp field-result) | 672 | (if (and (stringp field-result) |
| 673 | (not (eq cur-char ?z))) ;offset does not truncate | ||
| 657 | (substring padded-result 0 desired-length) | 674 | (substring padded-result 0 desired-length) |
| 658 | padded-result) ;numbers don't truncate | 675 | padded-result) ;numbers don't truncate |
| 659 | padded-result))))) | 676 | padded-result))))) |
| @@ -698,6 +715,176 @@ Suggests replacing OLD-FORM with NEW-FORM." | |||
| 698 | (insert "\"" old-form "\" -- use " new-form "\n")) | 715 | (insert "\"" old-form "\" -- use " new-form "\n")) |
| 699 | (display-buffer "*Time-stamp-compatibility*")))) | 716 | (display-buffer "*Time-stamp-compatibility*")))) |
| 700 | 717 | ||
| 718 | ;;; A principled, expressive implementation of time zone offset | ||
| 719 | ;;; formatting ("%z" and variants). | ||
| 720 | |||
| 721 | ;;; * Overarching principle for %z | ||
| 722 | |||
| 723 | ;; The output should be clear and complete. | ||
| 724 | ;; | ||
| 725 | ;; That is, | ||
| 726 | ;; a) it should be unambiguous what offset is represented, and | ||
| 727 | ;; b) it should be possible to exactly recreate the offset. | ||
| 728 | |||
| 729 | ;;; * Principles for %z | ||
| 730 | |||
| 731 | ;; - The numeric fields are HHMMSS. | ||
| 732 | ;; - The fixed point is at the left. The first 2 digits are always | ||
| 733 | ;; hours, the next 2 (if they exist) minutes, and next 2 (if they | ||
| 734 | ;; exist) seconds. "+11" is 11 hours (not 11 minutes, not 11 seconds). | ||
| 735 | ;; "+1015" is 10 hours 15 minutes (not 10 minutes 15 seconds). | ||
| 736 | ;; - Each of the three numeric fields is two digits. | ||
| 737 | ;; "+1" and "+100" are illegal. (Is that 1 hour? 10 hours? 100 hours?) | ||
| 738 | ;; - The MMSS fields may be omitted only if both are 00. Thus, the width | ||
| 739 | ;; of the field depends on the data. (This is similar to how | ||
| 740 | ;; %B is always long enough to spell the entire month name.) | ||
| 741 | ;; - The SS field may be omitted only if it is 00. | ||
| 742 | ;; - Colons between the numeric fields are an option, unless the hours | ||
| 743 | ;; field is greater than 99, when colons are needed to prevent ambiguity. | ||
| 744 | ;; - If padding with zeros, we must pad on the right, because the | ||
| 745 | ;; fixed point is at the left. (This is similar to how %N, | ||
| 746 | ;; fractional seconds, must add its zeros on the right.) | ||
| 747 | ;; - After zero-padding has filled out minutes and seconds with zeros, | ||
| 748 | ;; further padding can be blanks only. | ||
| 749 | ;; Any additional zeros would be confusing. | ||
| 750 | |||
| 751 | ;;; * Padding for %z | ||
| 752 | |||
| 753 | ;; Padding is under-specified, so we had to make choices. | ||
| 754 | ;; | ||
| 755 | ;; Principles guiding our choices: | ||
| 756 | ;; | ||
| 757 | ;; - The syntax should be easy to remember and the effect predictable. | ||
| 758 | ;; - It should be possible to produces as many useful effects as possible. | ||
| 759 | ;; | ||
| 760 | ;; Padding choices: | ||
| 761 | ;; | ||
| 762 | ;; - By default, pad with spaces, as other formats with non-digits do. | ||
| 763 | ;; The "0" flag pads first with zeros, until seconds are filled out. | ||
| 764 | ;; - If padding with spaces, pad on the right. This is consistent with | ||
| 765 | ;; how zero-padding works. Padding on the right also keeps the fixed | ||
| 766 | ;; point in the same place, as other formats do for any given width. | ||
| 767 | ;; - The %_z format always outputs seconds, allowing all added padding | ||
| 768 | ;; to be spaces. Without this rule, there would be no way to | ||
| 769 | ;; request seconds that worked for both 2- and 3-digit hours. | ||
| 770 | ;; - Conflicting options are rejected, lest users depend | ||
| 771 | ;; on incidental behavior. | ||
| 772 | ;; | ||
| 773 | ;; Padding combos that make no sense and are thus disallowed: | ||
| 774 | ;; | ||
| 775 | ;; %-:z - minus minimizes to hours, : expands to minutes | ||
| 776 | ;; %-::z - minus minimizes to hours, :: expands to seconds | ||
| 777 | ;; %_:z - underscore requires seconds, : displays minutes | ||
| 778 | ;; %_:::z - underscore requires seconds, ::: minimizes to hours | ||
| 779 | ;; | ||
| 780 | ;; Example padding effects (with offsets of 99 and 100 hours): | ||
| 781 | ;; | ||
| 782 | ;; %-7z "+99 " "+100:00" | ||
| 783 | ;; %7z "+9900 " "+100:00" | ||
| 784 | ;; %07z "+990000" "+100:00" | ||
| 785 | ;; %_7z "+990000" "+100:00:00" | ||
| 786 | ;; | ||
| 787 | ;; %7:::z "+99 " "+100:00" | ||
| 788 | ;; %7:z "+99:00 " "+100:00" | ||
| 789 | ;; %07:z "+99:00:00" "+100:00" | ||
| 790 | ;; %7::z "+99:00:00" "+100:00:00" | ||
| 791 | |||
| 792 | ;;; * BNF syntax of the offset string produced by %z | ||
| 793 | |||
| 794 | ;; <offset> ::= <sign><hours>[<minutes>[<seconds>]]<padding> | | ||
| 795 | ;; <sign><hours>[<colonminutes>[<colonseconds>]]<padding> | | ||
| 796 | ;; <sign><bighours><colonminutes>[<colonseconds>]<padding> | ||
| 797 | ;; <sign> ::= "+"|"-" | ||
| 798 | ;; <hours> ::= <2digits> | ||
| 799 | ;; <minutes> ::= <2digits> | ||
| 800 | ;; <seconds> ::= <2digits> | ||
| 801 | ;; <colonminutes> ::= ":"<minutes> | ||
| 802 | ;; <colonseconds> ::= ":"<seconds> | ||
| 803 | ;; <2digits> ::= <digit><digit> | ||
| 804 | ;; <digit> ::= "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9" | ||
| 805 | ;; <bighours> ::= <digit>*<digit><2digits> | ||
| 806 | ;; <padding> ::= " "* | ||
| 807 | |||
| 808 | (defun time-stamp-formatz-from-parsed-options (flag-minimize | ||
| 809 | flag-pad-spaces-only | ||
| 810 | flag-pad-zeros-first | ||
| 811 | colon-count | ||
| 812 | field-width | ||
| 813 | offset-secs) | ||
| 814 | "Formats a time offset according to a %z variation. | ||
| 815 | The caller of this function must have already parsed the %z format | ||
| 816 | string; this function accepts just the parts of the format. | ||
| 817 | |||
| 818 | With no flags, the output includes hours and minutes: +-HHMM | ||
| 819 | unless there is a non-zero seconds part, in which case the seconds | ||
| 820 | are included: +-HHMMSS | ||
| 821 | |||
| 822 | FLAG-MINIMIZE is whether \"-\" was specified. If non-nil, the | ||
| 823 | output may be limited to hours if minutes and seconds are zero. | ||
| 824 | |||
| 825 | FLAG-PAD-SPACES-ONLY is whether \"_\" was specified. If non-nil, | ||
| 826 | seconds must be output, so that any padding can be spaces only. | ||
| 827 | |||
| 828 | FLAG-PAD-ZEROS-FIRST is whether \"0\" was specified. If non-nil, | ||
| 829 | padding to the requested FIELD-WIDTH (if any) is done by adding | ||
| 830 | 00 seconds before padding with spaces. | ||
| 831 | |||
| 832 | COLON-COUNT is the number of colons preceding the \"z\" (0-3). One or | ||
| 833 | two colons put that many colons in the output (+-HH:MM or +-HH:MM:SS). | ||
| 834 | Three colons outputs only hours if minutes and seconds are zero and | ||
| 835 | includes colon separators if minutes and seconds are output. | ||
| 836 | |||
| 837 | FIELD-WIDTH is a whole number giving the minimum number of characters | ||
| 838 | in the output; 0 specifies no minimum. Additional characters will be | ||
| 839 | added on the right if necessary. The added characters will be spaces | ||
| 840 | unless FLAG-PAD-ZEROS-FIRST is non-nil. | ||
| 841 | |||
| 842 | OFFSET-SECS is the time zone offset (in seconds east of UTC) to be | ||
| 843 | formatted according to the preceding parameters." | ||
| 844 | (let ((hrs (/ (abs offset-secs) 3600)) | ||
| 845 | (mins (/ (% (abs offset-secs) 3600) 60)) | ||
| 846 | (secs (% (abs offset-secs) 60)) | ||
| 847 | (result "")) | ||
| 848 | ;; valid option combo? | ||
| 849 | (cond | ||
| 850 | ((not (or (and flag-minimize (> colon-count 0)) | ||
| 851 | (and flag-pad-spaces-only (> colon-count 0)) | ||
| 852 | (and flag-pad-spaces-only flag-minimize) | ||
| 853 | (and flag-pad-spaces-only flag-pad-zeros-first) | ||
| 854 | (and flag-pad-zeros-first flag-minimize))) | ||
| 855 | (setq result (concat result (if (>= offset-secs 0) "+" "-"))) | ||
| 856 | (setq result (concat result (format "%02d" hrs))) | ||
| 857 | ;; Need minutes? | ||
| 858 | (cond | ||
| 859 | ((or (> hrs 99) | ||
| 860 | (> mins 0) | ||
| 861 | (> secs 0) | ||
| 862 | (not (or flag-minimize (= colon-count 3))) | ||
| 863 | (and (> field-width (length result)) | ||
| 864 | flag-pad-zeros-first)) | ||
| 865 | ;; Need colon before minutes? | ||
| 866 | (if (or (> colon-count 0) | ||
| 867 | (> hrs 99)) | ||
| 868 | (setq result (concat result ":"))) | ||
| 869 | (setq result (concat result (format "%02d" mins))) | ||
| 870 | ;; Need seconds, too? | ||
| 871 | (cond | ||
| 872 | ((or (> secs 0) | ||
| 873 | (= colon-count 2) | ||
| 874 | flag-pad-spaces-only | ||
| 875 | (and (> field-width (length result)) | ||
| 876 | flag-pad-zeros-first)) | ||
| 877 | ;; Need colon before seconds? | ||
| 878 | (if (or (> colon-count 0) | ||
| 879 | (> hrs 99)) | ||
| 880 | (setq result (concat result ":"))) | ||
| 881 | (setq result (concat result (format "%02d" secs))))))) | ||
| 882 | ;; Need padding? | ||
| 883 | (let ((needed-padding (- field-width (length result)))) | ||
| 884 | (if (> needed-padding 0) | ||
| 885 | (setq result (concat result (make-string needed-padding ?\s))))))) | ||
| 886 | result)) | ||
| 887 | |||
| 701 | (provide 'time-stamp) | 888 | (provide 'time-stamp) |
| 702 | 889 | ||
| 703 | ;;; time-stamp.el ends here | 890 | ;;; time-stamp.el ends here |
diff --git a/test/lisp/time-stamp-tests.el b/test/lisp/time-stamp-tests.el index b42271e4e51..e42a58a1685 100644 --- a/test/lisp/time-stamp-tests.el +++ b/test/lisp/time-stamp-tests.el | |||
| @@ -525,7 +525,7 @@ | |||
| 525 | (should (equal (time-stamp-string "%#Z" ref-time1) utc-abbr))))) | 525 | (should (equal (time-stamp-string "%#Z" ref-time1) utc-abbr))))) |
| 526 | 526 | ||
| 527 | (ert-deftest time-stamp-format-time-zone-offset () | 527 | (ert-deftest time-stamp-format-time-zone-offset () |
| 528 | "Tests time-stamp legacy format %z and new offset format %5z." | 528 | "Tests time-stamp legacy format %z and spot tests of new offset format %5z." |
| 529 | (with-time-stamp-test-env | 529 | (with-time-stamp-test-env |
| 530 | (let ((utc-abbr (format-time-string "%#Z" ref-time1 t))) | 530 | (let ((utc-abbr (format-time-string "%#Z" ref-time1 t))) |
| 531 | ;; documented 1995-2019, warned since 2019, will change | 531 | ;; documented 1995-2019, warned since 2019, will change |
| @@ -540,8 +540,9 @@ | |||
| 540 | (let ((time-stamp-time-zone "CET-1")) | 540 | (let ((time-stamp-time-zone "CET-1")) |
| 541 | (should (equal (time-stamp-string "%5z" ref-time1) "+0100"))) | 541 | (should (equal (time-stamp-string "%5z" ref-time1) "+0100"))) |
| 542 | ;; implemented since 2019, verify that these don't warn | 542 | ;; implemented since 2019, verify that these don't warn |
| 543 | ;; See also the "formatz" tests below, which since 2021 test more | ||
| 544 | ;; variants with more offsets. | ||
| 543 | (should (equal (time-stamp-string "%-z" ref-time1) "+00")) | 545 | (should (equal (time-stamp-string "%-z" ref-time1) "+00")) |
| 544 | (should (equal (time-stamp-string "%_z" ref-time1) "+0000")) | ||
| 545 | (should (equal (time-stamp-string "%:z" ref-time1) "+00:00")) | 546 | (should (equal (time-stamp-string "%:z" ref-time1) "+00:00")) |
| 546 | (should (equal (time-stamp-string "%::z" ref-time1) "+00:00:00")) | 547 | (should (equal (time-stamp-string "%::z" ref-time1) "+00:00:00")) |
| 547 | (should (equal (time-stamp-string "%9::z" ref-time1) "+00:00:00")) | 548 | (should (equal (time-stamp-string "%9::z" ref-time1) "+00:00:00")) |
| @@ -615,16 +616,24 @@ | |||
| 615 | (concat Mon "." MON "." Mon))) | 616 | (concat Mon "." MON "." Mon))) |
| 616 | ;; underscore flag is independent | 617 | ;; underscore flag is independent |
| 617 | (should (equal (time-stamp-string "%_d.%d.%_d" ref-time1) " 2.02. 2")) | 618 | (should (equal (time-stamp-string "%_d.%d.%_d" ref-time1) " 2.02. 2")) |
| 618 | ;; minus flag is independendent | 619 | (should (equal (time-stamp-string "%_7z.%7z.%_7z" ref-time1) |
| 620 | "+000000.+0000 .+000000")) | ||
| 621 | ;; minus flag is independent | ||
| 619 | (should (equal (time-stamp-string "%d.%-d.%d" ref-time1) "02.2.02")) | 622 | (should (equal (time-stamp-string "%d.%-d.%d" ref-time1) "02.2.02")) |
| 620 | ;; 0 flag is independendent | 623 | (should (equal (time-stamp-string "%3z.%-3z.%3z" ref-time1) |
| 624 | "+0000.+00.+0000")) | ||
| 625 | ;; 0 flag is independent | ||
| 621 | (should (equal (time-stamp-string "%2d.%02d.%2d" ref-time1) " 2.02. 2")) | 626 | (should (equal (time-stamp-string "%2d.%02d.%2d" ref-time1) " 2.02. 2")) |
| 627 | (should (equal (time-stamp-string "%6:::z.%06:::z.%6:::z" ref-time1) | ||
| 628 | "+00 .+00:00.+00 ")) | ||
| 622 | ;; field width is independent | 629 | ;; field width is independent |
| 623 | (should (equal | 630 | (should (equal |
| 624 | (time-stamp-string "%6Y.%Y.%6Y" ref-time1) " 2006.2006. 2006")) | 631 | (time-stamp-string "%6Y.%Y.%6Y" ref-time1) " 2006.2006. 2006")) |
| 625 | ;; colon modifier is independent | 632 | ;; colon modifier is independent |
| 626 | (should (equal (time-stamp-string "%a.%:a.%a" ref-time1) | 633 | (should (equal (time-stamp-string "%a.%:a.%a" ref-time1) |
| 627 | (concat Mon "." Monday "." Mon))) | 634 | (concat Mon "." Monday "." Mon))) |
| 635 | (should (equal (time-stamp-string "%5z.%5::z.%5z" ref-time1) | ||
| 636 | "+0000.+00:00:00.+0000")) | ||
| 628 | ;; format letter is independent | 637 | ;; format letter is independent |
| 629 | (should (equal (time-stamp-string "%H:%M" ref-time1) "15:04"))))) | 638 | (should (equal (time-stamp-string "%H:%M" ref-time1) "15:04"))))) |
| 630 | 639 | ||
| @@ -691,4 +700,401 @@ | |||
| 691 | (should (safe-local-variable-p 'time-stamp-pattern "a string")) | 700 | (should (safe-local-variable-p 'time-stamp-pattern "a string")) |
| 692 | (should-not (safe-local-variable-p 'time-stamp-pattern 17))) | 701 | (should-not (safe-local-variable-p 'time-stamp-pattern 17))) |
| 693 | 702 | ||
| 703 | ;;;; Setup for tests of time offset formatting with %z | ||
| 704 | |||
| 705 | (defun formatz (format zone) | ||
| 706 | "Uses time FORMAT string to format the offset of ZONE, returning the result. | ||
| 707 | FORMAT is \"%z\" or a variation. | ||
| 708 | ZONE is as the ZONE argument of the `format-time-string' function." | ||
| 709 | (with-time-stamp-test-env | ||
| 710 | (let ((time-stamp-time-zone zone)) | ||
| 711 | ;; Call your favorite time formatter here. | ||
| 712 | ;; For narrower-scope unit testing, | ||
| 713 | ;; instead of calling time-stamp-string here, | ||
| 714 | ;; we could directly call (format-time-offset format zone) | ||
| 715 | (time-stamp-string format) | ||
| 716 | ))) | ||
| 717 | |||
| 718 | (defun format-time-offset (format offset-secs) | ||
| 719 | "Uses FORMAT to format the time zone represented by OFFSET-SECS. | ||
| 720 | FORMAT must be \"%z\", possibly with a flag and padding. | ||
| 721 | This function is a wrapper around `time-stamp-formatz-from-parsed-options' | ||
| 722 | and is used for testing." | ||
| 723 | ;; This wrapper adds a simple regexp-based parser that handles only | ||
| 724 | ;; %z and variants. In normal use, time-stamp-formatz-from-parsed-options | ||
| 725 | ;; is called from a parser that handles all time string formats. | ||
| 726 | (string-match | ||
| 727 | "\\`\\([^%]*\\)%\\([-_]?\\)\\(0?\\)\\([1-9][0-9]*\\)?\\([EO]?\\)\\(:*\\)\\([^a-zA-Z]+\\)?z\\(.*\\)" | ||
| 728 | format) | ||
| 729 | (let ((leading-string (match-string 1 format)) | ||
| 730 | (flag-minimize (seq-find (lambda (x) (eq x ?-)) | ||
| 731 | (match-string 2 format))) | ||
| 732 | (flag-pad-with-spaces (seq-find (lambda (x) (eq x ?_)) | ||
| 733 | (match-string 2 format))) | ||
| 734 | (flag-pad-with-zeros (equal (match-string 3 format) "0")) | ||
| 735 | (field-width (string-to-number (or (match-string 4 format) ""))) | ||
| 736 | (colon-count (length (match-string 6 format))) | ||
| 737 | (garbage (match-string 7 format)) | ||
| 738 | (trailing-string (match-string 8 format))) | ||
| 739 | (concat leading-string | ||
| 740 | (if garbage | ||
| 741 | "" | ||
| 742 | (time-stamp-formatz-from-parsed-options flag-minimize | ||
| 743 | flag-pad-with-spaces | ||
| 744 | flag-pad-with-zeros | ||
| 745 | colon-count | ||
| 746 | field-width | ||
| 747 | offset-secs)) | ||
| 748 | trailing-string))) | ||
| 749 | |||
| 750 | (defun fz-make+zone (h &optional m s) | ||
| 751 | "Creates a non-negative offset." | ||
| 752 | (let ((m (or m 0)) | ||
| 753 | (s (or s 0))) | ||
| 754 | (+ (* 3600 h) (* 60 m) s))) | ||
| 755 | |||
| 756 | (defun fz-make-zone (h &optional m s) | ||
| 757 | "Creates a negative offset. The arguments are all non-negative." | ||
| 758 | (- (fz-make+zone h m s))) | ||
| 759 | |||
| 760 | (defmacro formatz-should-equal (zone expect) | ||
| 761 | "Formats ZONE and compares it to EXPECT. | ||
| 762 | Uses the free variables `form-string' and `pattern-mod'. | ||
| 763 | The functions in `pattern-mod' are composed left to right." | ||
| 764 | `(let ((result ,expect)) | ||
| 765 | (dolist (fn pattern-mod) | ||
| 766 | (setq result (funcall fn result))) | ||
| 767 | (should (equal (formatz form-string ,zone) result)))) | ||
| 768 | |||
| 769 | ;; These test cases have zeros in all places (first, last, none, both) | ||
| 770 | ;; for hours, minutes, and seconds. | ||
| 771 | |||
| 772 | (defun formatz-hours-exact-helper (form-string pattern-mod) | ||
| 773 | "Tests format %z with whole hours." | ||
| 774 | (formatz-should-equal (fz-make+zone 0) "+00") ;0 sign always +, both digits | ||
| 775 | (formatz-should-equal (fz-make+zone 10) "+10") | ||
| 776 | (formatz-should-equal (fz-make-zone 10) "-10") | ||
| 777 | (formatz-should-equal (fz-make+zone 2) "+02") | ||
| 778 | (formatz-should-equal (fz-make-zone 2) "-02") | ||
| 779 | (formatz-should-equal (fz-make+zone 13) "+13") | ||
| 780 | (formatz-should-equal (fz-make-zone 13) "-13") | ||
| 781 | ) | ||
| 782 | |||
| 783 | (defun formatz-nonzero-minutes-helper (form-string pattern-mod) | ||
| 784 | "Tests format %z with whole minutes." | ||
| 785 | (formatz-should-equal (fz-make+zone 0 30) "+00:30") ;has hours even though 0 | ||
| 786 | (formatz-should-equal (fz-make-zone 0 30) "-00:30") | ||
| 787 | (formatz-should-equal (fz-make+zone 0 4) "+00:04") | ||
| 788 | (formatz-should-equal (fz-make-zone 0 4) "-00:04") | ||
| 789 | (formatz-should-equal (fz-make+zone 8 40) "+08:40") | ||
| 790 | (formatz-should-equal (fz-make-zone 8 40) "-08:40") | ||
| 791 | (formatz-should-equal (fz-make+zone 0 15) "+00:15") | ||
| 792 | (formatz-should-equal (fz-make-zone 0 15) "-00:15") | ||
| 793 | (formatz-should-equal (fz-make+zone 11 30) "+11:30") | ||
| 794 | (formatz-should-equal (fz-make-zone 11 30) "-11:30") | ||
| 795 | (formatz-should-equal (fz-make+zone 3 17) "+03:17") | ||
| 796 | (formatz-should-equal (fz-make-zone 3 17) "-03:17") | ||
| 797 | (formatz-should-equal (fz-make+zone 12 45) "+12:45") | ||
| 798 | (formatz-should-equal (fz-make-zone 12 45) "-12:45") | ||
| 799 | ) | ||
| 800 | |||
| 801 | (defun formatz-nonzero-seconds-helper (form-string pattern-mod) | ||
| 802 | "Tests format %z with non-0 seconds." | ||
| 803 | ;; non-0 seconds are always included | ||
| 804 | (formatz-should-equal (fz-make+zone 0 0 50) "+00:00:50") | ||
| 805 | (formatz-should-equal (fz-make-zone 0 0 50) "-00:00:50") | ||
| 806 | (formatz-should-equal (fz-make+zone 0 0 06) "+00:00:06") | ||
| 807 | (formatz-should-equal (fz-make-zone 0 0 06) "-00:00:06") | ||
| 808 | (formatz-should-equal (fz-make+zone 0 7 50) "+00:07:50") | ||
| 809 | (formatz-should-equal (fz-make-zone 0 7 50) "-00:07:50") | ||
| 810 | (formatz-should-equal (fz-make+zone 0 0 16) "+00:00:16") | ||
| 811 | (formatz-should-equal (fz-make-zone 0 0 16) "-00:00:16") | ||
| 812 | (formatz-should-equal (fz-make+zone 0 12 36) "+00:12:36") | ||
| 813 | (formatz-should-equal (fz-make-zone 0 12 36) "-00:12:36") | ||
| 814 | (formatz-should-equal (fz-make+zone 0 3 45) "+00:03:45") | ||
| 815 | (formatz-should-equal (fz-make-zone 0 3 45) "-00:03:45") | ||
| 816 | (formatz-should-equal (fz-make+zone 8 45 30) "+08:45:30") | ||
| 817 | (formatz-should-equal (fz-make-zone 8 45 30) "-08:45:30") | ||
| 818 | (formatz-should-equal (fz-make+zone 0 11 45) "+00:11:45") | ||
| 819 | (formatz-should-equal (fz-make-zone 0 11 45) "-00:11:45") | ||
| 820 | (formatz-should-equal (fz-make+zone 3 20 15) "+03:20:15") | ||
| 821 | (formatz-should-equal (fz-make-zone 3 20 15) "-03:20:15") | ||
| 822 | (formatz-should-equal (fz-make+zone 11 14 30) "+11:14:30") | ||
| 823 | (formatz-should-equal (fz-make-zone 11 14 30) "-11:14:30") | ||
| 824 | (formatz-should-equal (fz-make+zone 12 30 49) "+12:30:49") | ||
| 825 | (formatz-should-equal (fz-make-zone 12 30 49) "-12:30:49") | ||
| 826 | (formatz-should-equal (fz-make+zone 12 0 34) "+12:00:34") | ||
| 827 | (formatz-should-equal (fz-make-zone 12 0 34) "-12:00:34") | ||
| 828 | ) | ||
| 829 | |||
| 830 | (defun formatz-hours-big-helper (form-string pattern-mod) | ||
| 831 | "Tests format %z with hours that don't fit in two digits." | ||
| 832 | (formatz-should-equal (fz-make+zone 101) "+101:00") | ||
| 833 | (formatz-should-equal (fz-make+zone 123 10) "+123:10") | ||
| 834 | (formatz-should-equal (fz-make-zone 123 10) "-123:10") | ||
| 835 | (formatz-should-equal (fz-make+zone 123 2) "+123:02") | ||
| 836 | (formatz-should-equal (fz-make-zone 123 2) "-123:02") | ||
| 837 | ) | ||
| 838 | |||
| 839 | (defun formatz-seconds-big-helper (form-string pattern-mod) | ||
| 840 | "Tests format %z with hours greater than 99 and non-zero seconds." | ||
| 841 | (formatz-should-equal (fz-make+zone 123 0 30) "+123:00:30") | ||
| 842 | (formatz-should-equal (fz-make-zone 123 0 30) "-123:00:30") | ||
| 843 | (formatz-should-equal (fz-make+zone 120 0 4) "+120:00:04") | ||
| 844 | (formatz-should-equal (fz-make-zone 120 0 4) "-120:00:04") | ||
| 845 | ) | ||
| 846 | |||
| 847 | ;; Functions that modify the expected output string, so that we can | ||
| 848 | ;; use the above test cases for multiple formats. | ||
| 849 | |||
| 850 | (defun formatz-mod-del-colons (string) | ||
| 851 | "Returns STRING with any colons removed." | ||
| 852 | (replace-regexp-in-string ":" "" string)) | ||
| 853 | |||
| 854 | (defun formatz-mod-add-00 (string) | ||
| 855 | "Returns STRING with \"00\" appended." | ||
| 856 | (concat string "00")) | ||
| 857 | |||
| 858 | (defun formatz-mod-add-colon00 (string) | ||
| 859 | "Returns STRING with \":00\" appended." | ||
| 860 | (concat string ":00")) | ||
| 861 | |||
| 862 | (defun formatz-mod-pad-r10 (string) | ||
| 863 | "Returns STRING padded on the right to 10 characters." | ||
| 864 | (concat string (make-string (- 10 (length string)) ?\s))) | ||
| 865 | |||
| 866 | (defun formatz-mod-pad-r12 (string) | ||
| 867 | "Returns STRING padded on the right to 12 characters." | ||
| 868 | (concat string (make-string (- 12 (length string)) ?\s))) | ||
| 869 | |||
| 870 | ;; Convenience macro for generating groups of test cases. | ||
| 871 | |||
| 872 | (defmacro formatz-generate-tests | ||
| 873 | (form-strings hour-mod mins-mod secs-mod big-mod secbig-mod) | ||
| 874 | "Defines ert-deftest tests for time formats FORM-STRINGS. | ||
| 875 | FORM-STRINGS is a list of formats, each \"%z\" or some variation thereof. | ||
| 876 | |||
| 877 | Each of the remaining arguments is an unquoted list of the form | ||
| 878 | (SAMPLE-OUTPUT . MODIFIERS). SAMPLE-OUTPUT is the result of the | ||
| 879 | FORM-STRINGS for a particular offset, detailed below for each argument. | ||
| 880 | The remaining elements of the list, the MODIFIERS, are the names of | ||
| 881 | functions to modify the expected results for sets of tests. | ||
| 882 | The MODIFIERS do not modify the SAMPLE-OUTPUT. | ||
| 883 | |||
| 884 | The one, literal sample output is given in the call to this macro | ||
| 885 | to provide a visual check at the call site that the format | ||
| 886 | behaves as expected. | ||
| 887 | |||
| 888 | HOUR-MOD is the result for offset 0 and modifiers for the other | ||
| 889 | expected results for whole hours. | ||
| 890 | MINS-MOD is the result for offset +30 minutes and modifiers for the | ||
| 891 | other expected results for whole minutes. | ||
| 892 | SECS-MOD is the result for offset +30 seconds and modifiers for the | ||
| 893 | other expected results for offsets with non-zero seconds. | ||
| 894 | BIG-MOD is the result for offset +100 hours and modifiers for the other | ||
| 895 | expected results for hours greater than 99 with a whole number of minutes. | ||
| 896 | SECBIG-MOD is the result for offset +100 hours 30 seconds and modifiers for | ||
| 897 | the other expected results for hours greater than 99 with non-zero seconds." | ||
| 898 | (declare (indent 1)) | ||
| 899 | ;; Generate a form to create a list of tests to define. When this | ||
| 900 | ;; macro is called, the form is evaluated, thus defining the tests. | ||
| 901 | (let ((ert-test-list '(list))) | ||
| 902 | (dolist (form-string form-strings ert-test-list) | ||
| 903 | (nconc | ||
| 904 | ert-test-list | ||
| 905 | (list | ||
| 906 | `(ert-deftest ,(intern (concat "formatz-" form-string "-hhmm")) () | ||
| 907 | (should (equal (formatz ,form-string (fz-make+zone 0)) | ||
| 908 | ,(car hour-mod))) | ||
| 909 | (formatz-hours-exact-helper ,form-string ',(cdr hour-mod)) | ||
| 910 | (should (equal (formatz ,form-string (fz-make+zone 0 30)) | ||
| 911 | ,(car mins-mod))) | ||
| 912 | (formatz-nonzero-minutes-helper ,form-string ',(cdr mins-mod))) | ||
| 913 | `(ert-deftest ,(intern (concat "formatz-" form-string "-secs")) () | ||
| 914 | (should (equal (formatz ,form-string (fz-make+zone 0 0 30)) | ||
| 915 | ,(car secs-mod))) | ||
| 916 | (formatz-nonzero-seconds-helper ,form-string ',(cdr secs-mod))) | ||
| 917 | `(ert-deftest ,(intern (concat "formatz-" form-string "-big")) () | ||
| 918 | (should (equal (formatz ,form-string (fz-make+zone 100)) | ||
| 919 | ,(car big-mod))) | ||
| 920 | (formatz-hours-big-helper ,form-string ',(cdr big-mod)) | ||
| 921 | (should (equal (formatz ,form-string (fz-make+zone 100 0 30)) | ||
| 922 | ,(car secbig-mod))) | ||
| 923 | (formatz-seconds-big-helper ,form-string ',(cdr secbig-mod))) | ||
| 924 | ))))) | ||
| 925 | |||
| 926 | ;;;; The actual test cases for %z | ||
| 927 | |||
| 928 | ;;; %z formats without colons. | ||
| 929 | |||
| 930 | ;; Option character "-" (minus) minimizes; it removes "00" minutes. | ||
| 931 | (formatz-generate-tests ("%-z" "%-3z") | ||
| 932 | ("+00") | ||
| 933 | ("+0030" formatz-mod-del-colons) | ||
| 934 | ("+000030" formatz-mod-del-colons) | ||
| 935 | ("+100:00") | ||
| 936 | ("+100:00:30")) | ||
| 937 | ;; Tests that minus with padding pads with spaces. | ||
| 938 | (formatz-generate-tests ("%-12z") | ||
| 939 | ("+00 " formatz-mod-pad-r12) | ||
| 940 | ("+0030 " formatz-mod-del-colons formatz-mod-pad-r12) | ||
| 941 | ("+000030 " formatz-mod-del-colons formatz-mod-pad-r12) | ||
| 942 | ("+100:00 " formatz-mod-pad-r12) | ||
| 943 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 944 | ;; Tests that 0 after other digits becomes padding of ten, not zero flag. | ||
| 945 | (formatz-generate-tests ("%-10z") | ||
| 946 | ("+00 " formatz-mod-pad-r10) | ||
| 947 | ("+0030 " formatz-mod-del-colons formatz-mod-pad-r10) | ||
| 948 | ("+000030 " formatz-mod-del-colons formatz-mod-pad-r10) | ||
| 949 | ("+100:00 " formatz-mod-pad-r10) | ||
| 950 | ("+100:00:30")) | ||
| 951 | |||
| 952 | ;; Although time-stamp doesn't call us for %z, we do want to spot-check | ||
| 953 | ;; it here, to verify the implementation we will eventually use. | ||
| 954 | ;; The legacy exception for %z in time-stamp will need to remain | ||
| 955 | ;; through at least 2024 and Emacs 28. | ||
| 956 | (ert-deftest formatz-%z-spotcheck () | ||
| 957 | (should (equal (format-time-offset "%z" (fz-make+zone 0)) "+0000")) | ||
| 958 | (should (equal (format-time-offset "%z" (fz-make+zone 0 30)) "+0030")) | ||
| 959 | (should (equal (format-time-offset "%z" (fz-make+zone 0 0 30)) "+000030")) | ||
| 960 | (should (equal (format-time-offset "%z" (fz-make+zone 100)) "+100:00")) | ||
| 961 | (should (equal (format-time-offset "%z" (fz-make+zone 100 0 30)) "+100:00:30")) | ||
| 962 | ) | ||
| 963 | |||
| 964 | ;; Basic %z outputs 4 digits. | ||
| 965 | ;; Small padding values do not extend the result. | ||
| 966 | (formatz-generate-tests (;; We don't check %z here because time-stamp | ||
| 967 | ;; has a legacy behavior for it. | ||
| 968 | ;;"%z" | ||
| 969 | "%5z" "%0z" "%05z") | ||
| 970 | ("+0000" formatz-mod-add-00) | ||
| 971 | ("+0030" formatz-mod-del-colons) | ||
| 972 | ("+000030" formatz-mod-del-colons) | ||
| 973 | ("+100:00") | ||
| 974 | ("+100:00:30")) | ||
| 975 | |||
| 976 | ;; Tests that padding adds spaces. | ||
| 977 | (formatz-generate-tests ("%12z") | ||
| 978 | ("+0000 " formatz-mod-add-00 formatz-mod-pad-r12) | ||
| 979 | ("+0030 " formatz-mod-del-colons formatz-mod-pad-r12) | ||
| 980 | ("+000030 " formatz-mod-del-colons formatz-mod-pad-r12) | ||
| 981 | ("+100:00 " formatz-mod-pad-r12) | ||
| 982 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 983 | |||
| 984 | ;; Requiring 0-padding to 6 adds seconds (only) as needed. | ||
| 985 | (formatz-generate-tests ("%06z") | ||
| 986 | ("+000000" formatz-mod-add-00 formatz-mod-add-00) | ||
| 987 | ("+003000" formatz-mod-del-colons formatz-mod-add-00) | ||
| 988 | ("+000030" formatz-mod-del-colons) | ||
| 989 | ("+100:00") | ||
| 990 | ("+100:00:30")) | ||
| 991 | |||
| 992 | ;; Option character "_" always adds seconds. | ||
| 993 | (formatz-generate-tests ("%_z" "%_7z") | ||
| 994 | ("+000000" formatz-mod-add-00 formatz-mod-add-00) | ||
| 995 | ("+003000" formatz-mod-del-colons formatz-mod-add-00) | ||
| 996 | ("+000030" formatz-mod-del-colons) | ||
| 997 | ("+100:00:00" formatz-mod-add-colon00) | ||
| 998 | ("+100:00:30")) | ||
| 999 | |||
| 1000 | ;; Enough 0-padding adds seconds, then adds spaces. | ||
| 1001 | (formatz-generate-tests ("%012z" "%_12z") | ||
| 1002 | ("+000000 " formatz-mod-add-00 formatz-mod-add-00 formatz-mod-pad-r12) | ||
| 1003 | ("+003000 " formatz-mod-del-colons formatz-mod-add-00 formatz-mod-pad-r12) | ||
| 1004 | ("+000030 " formatz-mod-del-colons formatz-mod-pad-r12) | ||
| 1005 | ("+100:00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12) | ||
| 1006 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 1007 | |||
| 1008 | ;;; %z formats with colons | ||
| 1009 | |||
| 1010 | ;; Three colons can output hours only, | ||
| 1011 | ;; like %-z, but uses colons with non-zero minutes and seconds. | ||
| 1012 | (formatz-generate-tests ("%:::z" "%0:::z" | ||
| 1013 | "%3:::z" "%03:::z") | ||
| 1014 | ("+00") | ||
| 1015 | ("+00:30") | ||
| 1016 | ("+00:00:30") | ||
| 1017 | ("+100:00") | ||
| 1018 | ("+100:00:30")) | ||
| 1019 | |||
| 1020 | ;; Padding with three colons adds spaces | ||
| 1021 | (formatz-generate-tests ("%12:::z") | ||
| 1022 | ("+00 " formatz-mod-pad-r12) | ||
| 1023 | ("+00:30 " formatz-mod-pad-r12) | ||
| 1024 | ("+00:00:30 " formatz-mod-pad-r12) | ||
| 1025 | ("+100:00 " formatz-mod-pad-r12) | ||
| 1026 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 1027 | ;; Tests that 0 after other digits becomes padding of ten, not zero flag. | ||
| 1028 | (formatz-generate-tests ("%10:::z") | ||
| 1029 | ("+00 " formatz-mod-pad-r10) | ||
| 1030 | ("+00:30 " formatz-mod-pad-r10) | ||
| 1031 | ("+00:00:30 " formatz-mod-pad-r10) | ||
| 1032 | ("+100:00 " formatz-mod-pad-r10) | ||
| 1033 | ("+100:00:30")) | ||
| 1034 | |||
| 1035 | ;; One colon outputs minutes, like %z but with colon. | ||
| 1036 | (formatz-generate-tests ("%:z" "%6:z" "%0:z" "%06:z" "%06:::z") | ||
| 1037 | ("+00:00" formatz-mod-add-colon00) | ||
| 1038 | ("+00:30") | ||
| 1039 | ("+00:00:30") | ||
| 1040 | ("+100:00") | ||
| 1041 | ("+100:00:30")) | ||
| 1042 | |||
| 1043 | ;; Padding with one colon adds spaces | ||
| 1044 | (formatz-generate-tests ("%12:z") | ||
| 1045 | ("+00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12) | ||
| 1046 | ("+00:30 " formatz-mod-pad-r12) | ||
| 1047 | ("+00:00:30 " formatz-mod-pad-r12) | ||
| 1048 | ("+100:00 " formatz-mod-pad-r12) | ||
| 1049 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 1050 | |||
| 1051 | ;; Requiring 0-padding to 7 adds seconds (only) as needed. | ||
| 1052 | (formatz-generate-tests ("%07:z" "%07:::z") | ||
| 1053 | ("+00:00:00" formatz-mod-add-colon00 formatz-mod-add-colon00) | ||
| 1054 | ("+00:30:00" formatz-mod-add-colon00) | ||
| 1055 | ("+00:00:30") | ||
| 1056 | ("+100:00") | ||
| 1057 | ("+100:00:30")) | ||
| 1058 | |||
| 1059 | ;; Two colons outputs HH:MM:SS, like %_z but with colons. | ||
| 1060 | (formatz-generate-tests ("%::z" "%9::z" "%0::z" "%09::z") | ||
| 1061 | ("+00:00:00" formatz-mod-add-colon00 formatz-mod-add-colon00) | ||
| 1062 | ("+00:30:00" formatz-mod-add-colon00) | ||
| 1063 | ("+00:00:30") | ||
| 1064 | ("+100:00:00" formatz-mod-add-colon00) | ||
| 1065 | ("+100:00:30")) | ||
| 1066 | |||
| 1067 | ;; Enough padding adds minutes and seconds, then adds spaces. | ||
| 1068 | (formatz-generate-tests ("%012:z" "%012::z" "%12::z" "%012:::z") | ||
| 1069 | ("+00:00:00 " formatz-mod-add-colon00 formatz-mod-add-colon00 | ||
| 1070 | formatz-mod-pad-r12) | ||
| 1071 | ("+00:30:00 " formatz-mod-add-colon00 formatz-mod-pad-r12) | ||
| 1072 | ("+00:00:30 " formatz-mod-pad-r12) | ||
| 1073 | ("+100:00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12) | ||
| 1074 | ("+100:00:30 " formatz-mod-pad-r12)) | ||
| 1075 | |||
| 1076 | ;;; Illegal %z formats | ||
| 1077 | |||
| 1078 | (ert-deftest formatz-illegal-options () | ||
| 1079 | "Tests that illegal/nonsensical/ambiguous %z formats don't produce output." | ||
| 1080 | ;; multiple options | ||
| 1081 | (should (equal "" (formatz "%_-z" 0))) | ||
| 1082 | (should (equal "" (formatz "%-_z" 0))) | ||
| 1083 | (should (equal "" (formatz "%_0z" 0))) | ||
| 1084 | (should (equal "" (formatz "%0_z" 0))) | ||
| 1085 | (should (equal "" (formatz "%0-z" 0))) | ||
| 1086 | (should (equal "" (formatz "%-0z" 0))) | ||
| 1087 | ;; inconsistent to both minimize and require mins or secs | ||
| 1088 | (should (equal "" (formatz "%-:z" 0))) | ||
| 1089 | (should (equal "" (formatz "%-::z" 0))) | ||
| 1090 | ;; consistent, but redundant | ||
| 1091 | (should (equal "" (formatz "%-:::z" 0))) | ||
| 1092 | (should (equal "" (formatz "%_::z" 0))) | ||
| 1093 | ;; inconsistent to both pre-expand and default to hours or mins | ||
| 1094 | (should (equal "" (formatz "%_:::z" 0))) | ||
| 1095 | (should (equal "" (formatz "%_:z" 0))) | ||
| 1096 | ;; options that don't make sense with %z | ||
| 1097 | (should (equal "" (formatz "%#z" 0))) | ||
| 1098 | ) | ||
| 1099 | |||
| 694 | ;;; time-stamp-tests.el ends here | 1100 | ;;; time-stamp-tests.el ends here |