├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .mddoc.xml ├── .php-cs-fixer.dist.php ├── .php_cs ├── LICENSE.md ├── Makefile ├── README.md ├── composer.json ├── config.rb ├── example ├── basic.php └── simple.php ├── phpcs.xml.dist ├── phpstan.neon ├── phpunit.xml.dist ├── src ├── SimpleCalendar.php └── css │ ├── SimpleCalendar.css │ └── SimpleCalendar.scss └── test └── SimpleCalendarTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,php,html,scss}] 2 | charset = utf-8 3 | indent_style = tab 4 | indent_size = 4 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | - pull_request 3 | - push 4 | 5 | name: CI 6 | 7 | jobs: 8 | run: 9 | name: Tests 10 | 11 | strategy: 12 | matrix: 13 | operating-system: [ubuntu-latest] 14 | php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 15 | 16 | runs-on: ${{ matrix.operating-system }} 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php-versions }} 26 | extensions: sockets, json, curl 27 | 28 | - name: Install dependencies with composer 29 | run: composer install 30 | 31 | - name: Test 32 | run: make test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .idea/ 3 | vendor/ 4 | /composer.lock 5 | /clover.xml 6 | *.cache 7 | -------------------------------------------------------------------------------- /.mddoc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | A very simple, easy to use PHP calendar rendering class. 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | files() 5 | ->in(__DIR__ . '/src') 6 | ->in(__DIR__ . '/test') 7 | ->in(__DIR__ . '/example') 8 | ->name('*.php'); 9 | 10 | return (new PhpCsFixer\Config) 11 | ->setUsingCache(true) 12 | ->setIndent("\t") 13 | ->setLineEnding("\n") 14 | //->setUsingLinter(false) 15 | ->setRiskyAllowed(true) 16 | ->setRules( 17 | [ 18 | '@PHPUnit60Migration:risky' => true, 19 | 'php_unit_test_case_static_method_calls' => [ 20 | 'call_type' => 'this', 21 | ], 22 | 23 | 'concat_space' => [ 24 | 'spacing' => 'one', 25 | ], 26 | 27 | 'visibility_required' => true, 28 | 'indentation_type' => true, 29 | 'no_useless_return' => true, 30 | 31 | 'switch_case_space' => true, 32 | 'switch_case_semicolon_to_colon' => true, 33 | 34 | 'array_syntax' => [ 'syntax' => 'short' ], 35 | 'list_syntax' => [ 'syntax' => 'short' ], 36 | 37 | 'no_leading_import_slash' => true, 38 | 'no_leading_namespace_whitespace' => true, 39 | 40 | 'no_whitespace_in_blank_line' => true, 41 | 42 | 'phpdoc_add_missing_param_annotation' => [ 'only_untyped' => true, ], 43 | 'phpdoc_indent' => true, 44 | 45 | 'phpdoc_no_alias_tag' => true, 46 | 'phpdoc_no_package' => true, 47 | 'phpdoc_no_useless_inheritdoc' => true, 48 | 49 | 'phpdoc_order' => true, 50 | 'phpdoc_scalar' => true, 51 | 'phpdoc_single_line_var_spacing' => true, 52 | 53 | 'phpdoc_var_annotation_correct_order' => true, 54 | 55 | 'phpdoc_trim' => true, 56 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 57 | 58 | 'phpdoc_types' => true, 59 | 'phpdoc_types_order' => [ 60 | 'null_adjustment' => 'always_last', 61 | 'sort_algorithm' => 'alpha', 62 | ], 63 | 64 | 'phpdoc_align' => [ 65 | 'align' => 'vertical', 66 | 'tags' => [ 'param' ], 67 | ], 68 | 69 | 'phpdoc_line_span' => [ 70 | 'const' => 'single', 71 | 'method' => 'multi', 72 | 'property' => 'single', 73 | ], 74 | 75 | 'short_scalar_cast' => true, 76 | 77 | 'standardize_not_equals' => true, 78 | 'ternary_operator_spaces' => true, 79 | 'no_spaces_after_function_name' => true, 80 | 'no_unneeded_control_parentheses' => true, 81 | 82 | 'return_type_declaration' => [ 83 | 'space_before' => 'one', 84 | ], 85 | 86 | 'single_line_after_imports' => true, 87 | 'single_blank_line_before_namespace' => true, 88 | 'blank_line_after_namespace' => true, 89 | 'single_blank_line_at_eof' => true, 90 | 'ternary_to_null_coalescing' => true, 91 | 'whitespace_after_comma_in_array' => true, 92 | 93 | 'cast_spaces' => [ 'space' => 'none' ], 94 | 95 | 'encoding' => true, 96 | 97 | 'space_after_semicolon' => [ 98 | 'remove_in_empty_for_expressions' => true, 99 | ], 100 | 101 | 'align_multiline_comment' => [ 102 | 'comment_type' => 'phpdocs_like', 103 | ], 104 | 105 | 'blank_line_before_statement' => [ 106 | 'statements' => [ 'continue', 'try', 'switch', 'exit', 'throw', 'return', 'do' ], 107 | ], 108 | 109 | 'no_superfluous_phpdoc_tags' => [ 110 | 'remove_inheritdoc' => true, 111 | ], 112 | 'no_superfluous_elseif' => true, 113 | 114 | 'no_useless_else' => true, 115 | 116 | 'combine_consecutive_issets' => true, 117 | 'escape_implicit_backslashes' => true, 118 | 'explicit_indirect_variable' => true, 119 | 'heredoc_to_nowdoc' => true, 120 | 121 | 122 | 'no_singleline_whitespace_before_semicolons' => true, 123 | 'no_null_property_initialization' => true, 124 | 'no_whitespace_before_comma_in_array' => true, 125 | 126 | 'no_empty_phpdoc' => true, 127 | 'no_empty_statement' => true, 128 | 'no_empty_comment' => true, 129 | 'no_extra_blank_lines' => true, 130 | 'no_blank_lines_after_phpdoc' => true, 131 | 132 | 'no_spaces_around_offset' => [ 133 | 'positions' => [ 'outside' ], 134 | ], 135 | 136 | 'return_assignment' => true, 137 | 'lowercase_static_reference' => true, 138 | 139 | 'method_chaining_indentation' => true, 140 | 'method_argument_space' => [ 141 | 'on_multiline' => 'ignore', // at least until they fix it 142 | 'keep_multiple_spaces_after_comma' => true, 143 | ], 144 | 145 | 'multiline_comment_opening_closing' => true, 146 | 147 | 'include' => true, 148 | 'elseif' => true, 149 | 150 | 'simple_to_complex_string_variable' => true, 151 | 152 | 'global_namespace_import' => [ 153 | 'import_classes' => false, 154 | 'import_constants' => false, 155 | 'import_functions' => false, 156 | ], 157 | 158 | 'trailing_comma_in_multiline' => true, 159 | 'single_line_comment_style' => true, 160 | 161 | 'is_null' => true, 162 | 'yoda_style' => [ 163 | 'equal' => false, 164 | 'identical' => false, 165 | 'less_and_greater' => null, 166 | ], 167 | ] 168 | ) 169 | ->setFinder($finder); 170 | 171 | 172 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | files() 5 | ->in(__DIR__ . '/src') 6 | ->in(__DIR__ . '/test') 7 | ->in(__DIR__ . '/example') 8 | ->name('*.php'); 9 | 10 | 11 | return PhpCsFixer\Config::create() 12 | ->setUsingCache(true) 13 | ->setIndent("\t") 14 | ->setLineEnding("\n") 15 | //->setUsingLinter(false) 16 | //->setRiskyAllowed(true) 17 | ->setRules( 18 | [ 19 | 'concat_space' => [ 20 | 'spacing' => 'one', 21 | ], 22 | 23 | 'visibility_required' => true, 24 | 'indentation_type' => true, 25 | 'no_useless_return' => true, 26 | 27 | 'switch_case_space' => true, 28 | 'switch_case_semicolon_to_colon' => true, 29 | 30 | 'array_syntax' => [ 'syntax' => 'short' ], 31 | 'list_syntax' => [ 'syntax' => 'short' ], 32 | 33 | 'no_leading_import_slash' => true, 34 | 'no_leading_namespace_whitespace' => true, 35 | 36 | 'no_whitespace_in_blank_line' => true, 37 | 38 | 'phpdoc_add_missing_param_annotation' => [ 'only_untyped' => true, ], 39 | 'phpdoc_indent' => true, 40 | 41 | 'phpdoc_no_alias_tag' => true, 42 | 'phpdoc_no_package' => true, 43 | 'phpdoc_no_useless_inheritdoc' => true, 44 | 45 | 'phpdoc_order' => true, 46 | 'phpdoc_scalar' => true, 47 | 'phpdoc_single_line_var_spacing' => true, 48 | 49 | 'phpdoc_trim' => true, 50 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 51 | 52 | 'phpdoc_types' => true, 53 | 'phpdoc_types_order' => [ 54 | 'null_adjustment' => 'always_last', 55 | 'sort_algorithm' => 'alpha', 56 | ], 57 | 58 | 'short_scalar_cast' => true, 59 | 60 | 'standardize_not_equals' => true, 61 | 'ternary_operator_spaces' => true, 62 | 'no_spaces_after_function_name' => true, 63 | 'no_unneeded_control_parentheses' => true, 64 | 65 | 'return_type_declaration' => [ 66 | 'space_before' => 'one', 67 | ], 68 | 69 | 'single_line_after_imports' => true, 70 | 'single_blank_line_before_namespace' => true, 71 | 'blank_line_after_namespace' => true, 72 | 'single_blank_line_at_eof' => true, 73 | 'ternary_to_null_coalescing' => true, 74 | 'whitespace_after_comma_in_array' => true, 75 | 76 | 'cast_spaces' => [ 'space' => 'none' ], 77 | 78 | 'encoding' => true, 79 | 80 | 'space_after_semicolon' => [ 81 | 'remove_in_empty_for_expressions' => true, 82 | ], 83 | 84 | 'align_multiline_comment' => [ 85 | 'comment_type' => 'phpdocs_like', 86 | ], 87 | 88 | 'blank_line_before_statement' => [ 89 | 'statements' => [ 'continue', 'try', 'switch', 'die', 'exit', 'throw', 'return' ], 90 | ], 91 | 92 | 'no_superfluous_phpdoc_tags' => true, 93 | 'no_superfluous_elseif' => true, 94 | 95 | 'no_useless_else' => true, 96 | 97 | 'combine_consecutive_issets' => true, 98 | 'escape_implicit_backslashes' => true, 99 | 'heredoc_to_nowdoc' => true, 100 | 101 | 'no_empty_phpdoc' => true, 102 | 'no_empty_statement' => true, 103 | 'no_empty_comment' => true, 104 | 'no_extra_blank_lines' => true, 105 | 'no_blank_lines_after_phpdoc' => true, 106 | 107 | 'return_assignment' => true, 108 | 'lowercase_static_reference' => true, 109 | 110 | 'method_chaining_indentation' => true, 111 | 112 | 'elseif' => true, 113 | ] 114 | ) 115 | ->setFinder($finder); 116 | 117 | 118 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright (c) 2013 Jesse G. Donat 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_FILES = $(shell find example src -type f -name '*.php') 2 | 3 | .PHONY: test 4 | test: cs 5 | vendor/bin/phpunit 6 | vendor/bin/phpstan analyse --memory-limit 2g 7 | 8 | .PHONY: cs 9 | cs: 10 | vendor/bin/phpcs 11 | 12 | .PHONY: cbf 13 | cbf: 14 | vendor/bin/phpcbf 15 | 16 | .PHONY: fix 17 | fix: cbf 18 | vendor/bin/php-cs-fixer fix 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Calendar 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/donatj/simplecalendar/version)](https://packagist.org/packages/donatj/simplecalendar) 4 | [![License](https://poser.pugx.org/donatj/simplecalendar/license)](https://packagist.org/packages/donatj/simplecalendar) 5 | [![ci.yml](https://github.com/donatj/SimpleCalendar/actions/workflows/ci.yml/badge.svg?)](https://github.com/donatj/SimpleCalendar/actions/workflows/ci.yml) 6 | 7 | 8 | A very simple, easy to use PHP calendar rendering class. 9 | 10 | ## Requirements 11 | 12 | - **php**: >=7.2 13 | - **ext-calendar**: * 14 | 15 | ## Installing 16 | 17 | Install the latest version with: 18 | 19 | ```bash 20 | composer require 'donatj/simplecalendar' 21 | ``` 22 | 23 | ## Examples 24 | 25 | ```php 26 | '; 31 | 32 | $calendar = new donatj\SimpleCalendar('June 2010'); 33 | 34 | echo $calendar->render(); 35 | 36 | ``` 37 | 38 | ```php 39 | '; 43 | 44 | $calendar = new donatj\SimpleCalendar; 45 | 46 | $calendar->setStartOfWeek('Sunday'); 47 | $calendar->addDailyHtml('Sample Event', 'today', 'tomorrow'); 48 | 49 | $calendar->setWeekDayNames([ 'Sun', 'Mon', 'Tu', 'W', 'Th', 'F', 'Sa' ]); 50 | $calendar->setStartOfWeek('Monday'); 51 | 52 | echo $calendar->render(); 53 | 54 | ``` 55 | 56 | ## Documentation 57 | 58 | ### Class: \donatj\SimpleCalendar 59 | 60 | Simple Calendar 61 | 62 | #### Method: SimpleCalendar->__construct 63 | 64 | ```php 65 | function __construct([ $calendarDate = null [, $today = null]]) 66 | ``` 67 | 68 | ##### Parameters: 69 | 70 | - ***\DateTimeInterface*** | ***int*** | ***string*** | ***null*** `$calendarDate` 71 | - ***\DateTimeInterface*** | ***false*** | ***int*** | ***string*** | ***null*** `$today` 72 | 73 | --- 74 | 75 | #### Method: SimpleCalendar->setDate 76 | 77 | ```php 78 | function setDate([ $date = null]) : void 79 | ``` 80 | 81 | Sets the date for the calendar. 82 | 83 | ##### Parameters: 84 | 85 | - ***\DateTimeInterface*** | ***int*** | ***string*** | ***null*** `$date` - DateTimeInterface or Date string parsed by strtotime for the 86 | calendar date. If null set to current timestamp. 87 | 88 | **Throws**: `\Exception` 89 | 90 | --- 91 | 92 | #### Method: SimpleCalendar->setCalendarClasses 93 | 94 | ```php 95 | function setCalendarClasses(array $classes) : void 96 | ``` 97 | 98 | Sets the class names used in the calendar 99 | 100 | ```php 101 | [ 102 | 'calendar' => 'SimpleCalendar', 103 | 'leading_day' => 'SCprefix', 104 | 'trailing_day' => 'SCsuffix', 105 | 'today' => 'today', 106 | 'event' => 'event', 107 | 'events' => 'events', 108 | ] 109 | ``` 110 | 111 | ##### Parameters: 112 | 113 | - ***array*** `$classes` - Map of element to class names used by the calendar. 114 | 115 | --- 116 | 117 | #### Method: SimpleCalendar->setToday 118 | 119 | ```php 120 | function setToday([ $today = null]) : void 121 | ``` 122 | 123 | Sets "today"'s date. Defaults to today. 124 | 125 | ##### Parameters: 126 | 127 | - ***\DateTimeInterface*** | ***false*** | ***int*** | ***string*** | ***null*** `$today` - `null` will default to today, `false` will disable the 128 | rendering of Today. 129 | 130 | **Throws**: `\Exception` 131 | 132 | --- 133 | 134 | #### Method: SimpleCalendar->setWeekDayNames 135 | 136 | ```php 137 | function setWeekDayNames([ ?array $weekDayNames = null]) : void 138 | ``` 139 | 140 | ##### Parameters: 141 | 142 | - ***string[]*** | ***null*** `$weekDayNames` 143 | 144 | --- 145 | 146 | #### Method: SimpleCalendar->addDailyHtml 147 | 148 | ```php 149 | function addDailyHtml(string $html, $startDate [, $endDate = null]) : void 150 | ``` 151 | 152 | Add a daily event to the calendar 153 | 154 | ##### Parameters: 155 | 156 | - ***string*** `$html` - The raw HTML to place on the calendar for this event 157 | - ***\DateTimeInterface*** | ***int*** | ***string*** `$startDate` - Date string for when the event starts 158 | - ***\DateTimeInterface*** | ***int*** | ***string*** | ***null*** `$endDate` - Date string for when the event ends. Defaults to start date 159 | 160 | **Throws**: `\Exception` 161 | 162 | --- 163 | 164 | #### Method: SimpleCalendar->clearDailyHtml 165 | 166 | ```php 167 | function clearDailyHtml() : void 168 | ``` 169 | 170 | Clear all daily events for the calendar 171 | 172 | --- 173 | 174 | #### Method: SimpleCalendar->setStartOfWeek 175 | 176 | ```php 177 | function setStartOfWeek($offset) : void 178 | ``` 179 | 180 | Sets the first day of the week 181 | 182 | ##### Parameters: 183 | 184 | - ***int*** | ***string*** `$offset` - Day the week starts on. ex: "Monday" or 0-6 where 0 is Sunday 185 | 186 | --- 187 | 188 | #### Method: SimpleCalendar->show 189 | 190 | ```php 191 | function show([ bool $echo = true]) : string 192 | ``` 193 | 194 | Returns/Outputs the Calendar 195 | 196 | ##### DEPRECATED 197 | 198 | Use `render()` method instead. 199 | 200 | ##### Parameters: 201 | 202 | - ***bool*** `$echo` - Whether to echo resulting calendar 203 | 204 | ##### Returns: 205 | 206 | - ***string*** - HTML of the Calendar 207 | 208 | --- 209 | 210 | #### Method: SimpleCalendar->render 211 | 212 | ```php 213 | function render() : string 214 | ``` 215 | 216 | Returns the generated Calendar -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "donatj/simplecalendar", 3 | "type" : "library", 4 | "description": "A light, easy to use calendar rendering library", 5 | "keywords" : ["calendar"], 6 | "homepage" : "https://donatstudios.com/SimpleCalendar", 7 | "license" : "MIT", 8 | "require" : { 9 | "php": ">=7.2", 10 | "ext-calendar": "*" 11 | }, 12 | "require-dev": { 13 | "ext-dom": "*", 14 | "corpus/coding-standard": "^0.6.0", 15 | "donatj/drop": "^1.1", 16 | "friendsofphp/php-cs-fixer": "^3.3", 17 | "phpstan/phpstan": "^1.10", 18 | "phpunit/phpunit": "*", 19 | "squizlabs/php_codesniffer": "^3.7" 20 | }, 21 | "authors" : [ 22 | { 23 | "name" : "Jesse G. Donat", 24 | "email" : "donatj@gmail.com", 25 | "homepage": "https://donatstudios.com", 26 | "role" : "Developer" 27 | } 28 | ], 29 | "autoload" : { 30 | "psr-4": { 31 | "donatj\\": "src/" 32 | } 33 | }, 34 | "config": { 35 | "sort-packages": true, 36 | "allow-plugins": { 37 | "dealerdirect/phpcodesniffer-composer-installer": true 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "src/css" 6 | sass_dir = "src/css" 7 | images_dir = "includes/content/img" 8 | javascripts_dir = "includes/content/js" 9 | 10 | # You can select your preferred output style here (can be overridden via the command line): 11 | # output_style = :expanded or :nested or :compact or :compressed 12 | 13 | output_style = :nested 14 | 15 | # To enable relative paths to assets via compass helper functions. Uncomment: 16 | # relative_assets = true 17 | 18 | # To disable debugging comments that display the original location of your selectors. Uncomment: 19 | line_comments = false 20 | 21 | 22 | # If you prefer the indented syntax, you might want to regenerate this 23 | # project again passing --syntax sass, or you can uncomment this: 24 | # preferred_syntax = :sass 25 | # and then run: 26 | # sass-convert -R --from scss --to sass includes/content/scss scss && rm -rf sass && mv scss sass 27 | -------------------------------------------------------------------------------- /example/basic.php: -------------------------------------------------------------------------------- 1 | '; 5 | 6 | $calendar = new donatj\SimpleCalendar; 7 | 8 | $calendar->setStartOfWeek('Sunday'); 9 | $calendar->addDailyHtml('Sample Event', 'today', 'tomorrow'); 10 | 11 | $calendar->setWeekDayNames([ 'Sun', 'Mon', 'Tu', 'W', 'Th', 'F', 'Sa' ]); 12 | $calendar->setStartOfWeek('Monday'); 13 | 14 | echo $calendar->render(); 15 | -------------------------------------------------------------------------------- /example/simple.php: -------------------------------------------------------------------------------- 1 | '; 6 | 7 | $calendar = new donatj\SimpleCalendar('June 2010'); 8 | 9 | echo $calendar->render(); 10 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | src 5 | test 6 | example 7 | 8 | *.css 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 9 3 | paths: 4 | - src 5 | - test 6 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/SimpleCalendar.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://donatstudios.com 10 | * @license http://opensource.org/licenses/mit-license.php 11 | */ 12 | class SimpleCalendar { 13 | 14 | /** 15 | * Array of Week Day Names 16 | * 17 | * @var string[]|null 18 | */ 19 | private $weekDayNames; 20 | 21 | /** @var \DateTimeInterface */ 22 | private $now; 23 | 24 | /** @var \DateTimeInterface|null */ 25 | private $today; 26 | 27 | /** @var array */ 28 | private $classes = [ 29 | 'calendar' => 'SimpleCalendar', 30 | 'leading_day' => 'SCprefix', 31 | 'trailing_day' => 'SCsuffix', 32 | 'today' => 'today', 33 | 'event' => 'event', 34 | 'events' => 'events', 35 | ]; 36 | 37 | /** @var array>>> */ 38 | private $dailyHtml = []; 39 | /** @var int */ 40 | private $offset = 0; 41 | 42 | /** 43 | * @param \DateTimeInterface|int|string|null $calendarDate 44 | * @param \DateTimeInterface|false|int|string|null $today 45 | * 46 | * @see setDate 47 | * @see setToday 48 | */ 49 | public function __construct( $calendarDate = null, $today = null ) { 50 | $this->setDate($calendarDate); 51 | $this->setToday($today); 52 | } 53 | 54 | /** 55 | * Sets the date for the calendar. 56 | * 57 | * @param \DateTimeInterface|int|string|null $date DateTimeInterface or Date string parsed by strtotime for the 58 | * calendar date. If null set to current timestamp. 59 | * @throws \Exception 60 | */ 61 | public function setDate( $date = null ) : void { 62 | $this->now = $this->parseDate($date) ?: new \DateTimeImmutable; 63 | } 64 | 65 | /** 66 | * @param \DateTimeInterface|int|string|null $date 67 | * @throws \Exception 68 | */ 69 | private function parseDate( $date = null ) : ?\DateTimeInterface { 70 | if( $date instanceof \DateTimeInterface ) { 71 | return $date; 72 | } 73 | 74 | if( is_int($date) ) { 75 | return (new \DateTimeImmutable)->setTimestamp($date); 76 | } 77 | 78 | if( is_string($date) ) { 79 | return new \DateTimeImmutable($date); 80 | } 81 | 82 | return null; 83 | } 84 | 85 | /** 86 | * Sets the class names used in the calendar 87 | * 88 | * ```php 89 | * [ 90 | * 'calendar' => 'SimpleCalendar', 91 | * 'leading_day' => 'SCprefix', 92 | * 'trailing_day' => 'SCsuffix', 93 | * 'today' => 'today', 94 | * 'event' => 'event', 95 | * 'events' => 'events', 96 | * ] 97 | * ``` 98 | * 99 | * @param array $classes Map of element to class names used by the calendar. 100 | */ 101 | public function setCalendarClasses( array $classes ) : void { 102 | foreach( $classes as $key => $value ) { 103 | if( !isset($this->classes[$key]) ) { 104 | throw new \InvalidArgumentException("class '{$key}' not supported"); 105 | } 106 | 107 | $this->classes[$key] = $value; 108 | } 109 | } 110 | 111 | /** 112 | * Sets "today"'s date. Defaults to today. 113 | * 114 | * @param \DateTimeInterface|false|int|string|null $today `null` will default to today, `false` will disable the 115 | * rendering of Today. 116 | * @throws \Exception 117 | */ 118 | public function setToday( $today = null ) : void { 119 | if( $today === false ) { 120 | $this->today = null; 121 | } elseif( $today === null ) { 122 | $this->today = new \DateTimeImmutable; 123 | } else { 124 | $this->today = $this->parseDate($today); 125 | } 126 | } 127 | 128 | /** 129 | * @param string[]|null $weekDayNames 130 | */ 131 | public function setWeekDayNames( ?array $weekDayNames = null ) : void { 132 | if( is_array($weekDayNames) && count($weekDayNames) !== 7 ) { 133 | throw new \InvalidArgumentException('week array must have exactly 7 values'); 134 | } 135 | 136 | $this->weekDayNames = $weekDayNames ? array_values($weekDayNames) : null; 137 | } 138 | 139 | /** 140 | * Add a daily event to the calendar 141 | * 142 | * @param string $html The raw HTML to place on the calendar for this event 143 | * @param \DateTimeInterface|int|string $startDate Date string for when the event starts 144 | * @param \DateTimeInterface|int|string|null $endDate Date string for when the event ends. Defaults to start date 145 | * @throws \Exception 146 | */ 147 | public function addDailyHtml( string $html, $startDate, $endDate = null ) : void { 148 | /** @var int $htmlCount */ 149 | static $htmlCount = 0; 150 | 151 | $start = $this->parseDate($startDate); 152 | if( !$start ) { 153 | throw new \InvalidArgumentException('invalid start time'); 154 | } 155 | 156 | $end = $start; 157 | if( $endDate ) { 158 | $end = $this->parseDate($endDate); 159 | } 160 | 161 | if( !$end ) { 162 | throw new \InvalidArgumentException('invalid end time'); 163 | } 164 | 165 | if( $end->getTimestamp() < $start->getTimestamp() ) { 166 | throw new \InvalidArgumentException('end must come after start'); 167 | } 168 | 169 | $working = (new \DateTimeImmutable)->setTimestamp($start->getTimestamp()); 170 | 171 | do { 172 | $tDate = getdate($working->getTimestamp()); 173 | 174 | $this->dailyHtml[$tDate['year']][$tDate['mon']][$tDate['mday']][$htmlCount] = $html; 175 | 176 | $working = $working->add(new \DateInterval('P1D')); 177 | } while( $working->getTimestamp() < $end->getTimestamp() + 1 ); 178 | 179 | $htmlCount++; 180 | } 181 | 182 | /** 183 | * Clear all daily events for the calendar 184 | */ 185 | public function clearDailyHtml() : void { 186 | $this->dailyHtml = []; 187 | } 188 | 189 | /** 190 | * Sets the first day of the week 191 | * 192 | * @param int|string $offset Day the week starts on. ex: "Monday" or 0-6 where 0 is Sunday 193 | */ 194 | public function setStartOfWeek( $offset ) : void { 195 | if( is_int($offset) ) { 196 | $this->offset = $offset % 7; 197 | } elseif( $this->weekDayNames !== null && ($weekOffset = array_search($offset, $this->weekDayNames, true)) !== false ) { 198 | assert(is_int($weekOffset)); 199 | $this->offset = $weekOffset; 200 | } else { 201 | $weekTime = strtotime($offset); 202 | if( $weekTime === 0 || $weekTime === false ) { 203 | throw new \InvalidArgumentException('invalid offset'); 204 | } 205 | 206 | $date = date('N', $weekTime); 207 | assert($date !== false); 208 | 209 | $this->offset = intval($date) % 7; 210 | } 211 | } 212 | 213 | /** 214 | * Returns/Outputs the Calendar 215 | * 216 | * @param bool $echo Whether to echo resulting calendar 217 | * @return string HTML of the Calendar 218 | * @deprecated Use `render()` method instead. 219 | */ 220 | public function show( bool $echo = true ) : string { 221 | $out = $this->render(); 222 | if( $echo ) { 223 | echo $out; 224 | } 225 | 226 | return $out; 227 | } 228 | 229 | /** 230 | * Returns the generated Calendar 231 | */ 232 | public function render() : string { 233 | $now = getdate($this->now->getTimestamp()); 234 | $today = [ 'mday' => -1, 'mon' => -1, 'year' => -1 ]; 235 | if( $this->today !== null ) { 236 | $today = getdate($this->today->getTimestamp()); 237 | } 238 | 239 | $daysOfWeek = $this->weekdays(); 240 | $this->rotate($daysOfWeek, $this->offset); 241 | 242 | $time = mktime(0, 0, 1, $now['mon'], 1, $now['year']); 243 | assert($time !== false); 244 | 245 | $weekDayIndex = date('N', $time) - $this->offset; 246 | $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $now['mon'], $now['year']); 247 | 248 | $out = << 250 | TAG; 251 | 252 | foreach( $daysOfWeek as $dayName ) { 253 | $out .= "{$dayName}"; 254 | } 255 | 256 | $out .= <<<'TAG' 257 | 258 | 259 | 260 | TAG; 261 | 262 | $weekDayIndex = ($weekDayIndex + 7) % 7; 263 | 264 | $out .= str_repeat(<<  266 | TAG 267 | , $weekDayIndex); 268 | 269 | $count = $weekDayIndex + 1; 270 | for( $i = 1; $i <= $daysInMonth; $i++ ) { 271 | $date = (new \DateTimeImmutable)->setDate($now['year'], $now['mon'], $i); 272 | 273 | $isToday = false; 274 | if( $this->today !== null ) { 275 | $isToday = $i == $today['mday'] 276 | && $today['mon'] == $date->format('n') 277 | && $today['year'] == $date->format('Y'); 278 | } 279 | 280 | $out .= ''; 281 | 282 | $out .= sprintf('', $date->format('Y-m-d'), $i); 283 | 284 | $dailyHTML = null; 285 | if( isset($this->dailyHtml[$now['year']][$now['mon']][$i]) ) { 286 | $dailyHTML = $this->dailyHtml[$now['year']][$now['mon']][$i]; 287 | } 288 | 289 | if( is_array($dailyHTML) ) { 290 | $out .= '
'; 291 | foreach( $dailyHTML as $dHtml ) { 292 | $out .= sprintf('
%s
', $this->classes['event'], $dHtml); 293 | } 294 | 295 | $out .= '
'; 296 | } 297 | 298 | $out .= ''; 299 | 300 | if( $count > 6 ) { 301 | $out .= "\n" . ($i < $daysInMonth ? '' : ''); 302 | $count = 0; 303 | } 304 | 305 | $count++; 306 | } 307 | 308 | if( $count !== 1 ) { 309 | $out .= str_repeat(' ', 8 - $count) . ''; 310 | } 311 | 312 | $out .= "\n\n"; 313 | 314 | return $out; 315 | } 316 | 317 | /** 318 | * @param array $data 319 | */ 320 | private function rotate( array &$data, int $steps ) : void { 321 | $count = count($data); 322 | if( $steps < 0 ) { 323 | $steps = $count + $steps; 324 | } 325 | 326 | $steps %= $count; 327 | for( $i = 0; $i < $steps; $i++ ) { 328 | $data[] = array_shift($data); 329 | } 330 | } 331 | 332 | /** 333 | * @return string[] 334 | */ 335 | private function weekdays() : array { 336 | if( $this->weekDayNames !== null ) { 337 | return $this->weekDayNames; 338 | } 339 | 340 | $today = (86400 * (date('N'))); 341 | $wDays = []; 342 | for( $n = 0; $n < 7; $n++ ) { 343 | $wDays[] = date('D', time() - $today + ($n * 86400)); 344 | } 345 | 346 | return $wDays; 347 | } 348 | 349 | } 350 | -------------------------------------------------------------------------------- /src/css/SimpleCalendar.css: -------------------------------------------------------------------------------- 1 | table.SimpleCalendar { 2 | font-family: Arial; 3 | border-collapse: collapse; } 4 | table.SimpleCalendar th { 5 | background: #C9E9F0; 6 | color: #0092B4; 7 | font-size: 11px; 8 | padding: 3px; 9 | border: 1px solid #bbb; 10 | border-top: none; } 11 | table.SimpleCalendar tbody td { 12 | vertical-align: top; 13 | width: 90px; 14 | height: 90px; 15 | border: 1px solid #bbb; 16 | background: #e6e6e6; } 17 | table.SimpleCalendar tbody td time { 18 | font-size: .7em; 19 | display: block; 20 | background: white; 21 | padding: 2px; 22 | text-align: right; 23 | font-weight: bold; } 24 | table.SimpleCalendar tbody td.SCsuffix, table.SimpleCalendar tbody td.SCprefix { 25 | background: white; } 26 | table.SimpleCalendar tbody td div.event { 27 | color: #355; 28 | font-size: .65em; 29 | padding: 5px; 30 | line-height: 1em; 31 | border-bottom: 1px solid #bbb; 32 | background: #858585; 33 | color: white; } 34 | table.SimpleCalendar tbody td.today { 35 | background: #d2d2d2; } 36 | -------------------------------------------------------------------------------- /src/css/SimpleCalendar.scss: -------------------------------------------------------------------------------- 1 | 2 | table.SimpleCalendar { 3 | font-family: Arial; 4 | border-collapse: collapse; 5 | 6 | th { 7 | background: #C9E9F0; 8 | color: #0092B4; 9 | font-size: 11px; 10 | padding: 3px; 11 | border: 1px solid #bbb; 12 | border-top: none; 13 | } 14 | 15 | tbody { 16 | 17 | td { 18 | 19 | time { 20 | font-size: .7em; 21 | display: block; 22 | background: white; 23 | padding: 2px; 24 | text-align: right; 25 | font-weight: bold; 26 | } 27 | 28 | vertical-align: top; 29 | width: 90px; 30 | height: 90px;; 31 | border: 1px solid #bbb; 32 | background: rgb(230, 230, 230); 33 | 34 | &.SCsuffix, &.SCprefix { 35 | background: white; 36 | } 37 | 38 | div.event { 39 | 40 | color: #355; 41 | font-size: .65em; 42 | padding: 5px; 43 | line-height: 1em; 44 | border-bottom: 1px solid #bbb; 45 | background: rgb(133, 133, 133); 46 | color: white; 47 | 48 | } 49 | 50 | } 51 | 52 | td.today { 53 | background: rgb(210, 210, 210); 54 | } 55 | 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /test/SimpleCalendarTest.php: -------------------------------------------------------------------------------- 1 | assertNotFalse(strpos($cal->show(false), 'class="today"')); 12 | } 13 | 14 | public function testBadDailyHtmlDates() : void { 15 | try { 16 | $cal = new SimpleCalendar('June 2010', 'June 5 2010'); 17 | $cal->addDailyHtml('foo', 'tomorrow', 'yesterday'); 18 | } catch( InvalidArgumentException $ex ) { 19 | $this->assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $this->fail('expected InvalidArgumentException'); 25 | } 26 | 27 | public function testClasses() : void { 28 | $cal = new SimpleCalendar('June 2010', 'June 5 2010'); 29 | $defaults = [ 30 | 'SimpleCalendar', 31 | 'SCprefix', 32 | 'SCsuffix', 33 | 'today', 34 | 'event', 35 | 'events', 36 | ]; 37 | 38 | $cal->addDailyHtml('Sample Event', 'June 15 2010'); 39 | $cal_html = $cal->render(); 40 | foreach( $defaults as $class ) { 41 | $this->assertNotFalse(strpos($cal_html, 'class="' . $class . '"')); 42 | } 43 | } 44 | 45 | public function testCustomClasses() : void { 46 | $cal = new SimpleCalendar('June 2010', 'June 5 2010'); 47 | $classes = [ 48 | 'calendar' => 'TestCalendar', 49 | 'leading_day' => 'TestPrefix', 50 | 'trailing_day' => 'TestSuffix', 51 | 'today' => 'TestToday', 52 | 'event' => 'TestEvent', 53 | 'events' => 'TestEvents', 54 | ]; 55 | 56 | $cal->setCalendarClasses($classes); 57 | $cal->addDailyHtml('Sample Event', 'June 15 2010'); 58 | $cal_html = $cal->render(); 59 | 60 | foreach( $classes as $class ) { 61 | $this->assertNotFalse(strpos($cal_html, 'class="' . $class . '"')); 62 | } 63 | } 64 | 65 | public function testCurrentMonth_yearRegression() : void { 66 | $cal = new SimpleCalendar(sprintf('%d-%d-%d', (date('Y') - 1), date('n'), date('d'))); 67 | $this->assertFalse(strpos($cal->show(false), 'class="today"')); 68 | } 69 | 70 | public function testTagOpenCloseMismatch_regression() : void { 71 | $cal = new SimpleCalendar; 72 | $cal->setStartOfWeek(4); 73 | $cal->setDate('September 2016'); 74 | $html = $cal->show(false); 75 | 76 | $this->assertSame(substr_count($html, 'assertSame(substr_count($html, 'setDate('January 2017'); 83 | $html = $cal->show(false); 84 | 85 | $this->assertSame(substr_count($html, 'assertSame(substr_count($html, 'parseCalendarHtml($cal); 93 | 94 | $expected = [ 95 | [ 96 | [ 'class' => '', 'text' => 'Sun', ], 97 | [ 'class' => '', 'text' => 'Mon', ], 98 | [ 'class' => '', 'text' => 'Tue', ], 99 | [ 'class' => '', 'text' => 'Wed', ], 100 | [ 'class' => '', 'text' => 'Thu', ], 101 | [ 'class' => '', 'text' => 'Fri', ], 102 | [ 'class' => '', 'text' => 'Sat', ], 103 | ], 104 | 105 | [ 106 | [ 'class' => 'SCprefix', 'text' => ' ', ], 107 | [ 'class' => 'SCprefix', 'text' => ' ', ], 108 | [ 'class' => 'SCprefix', 'text' => ' ', ], 109 | [ 'class' => '', 'text' => '1', 'date' => '2016-06-01', ], 110 | [ 'class' => '', 'text' => '2', 'date' => '2016-06-02', ], 111 | [ 'class' => '', 'text' => '3', 'date' => '2016-06-03', ], 112 | [ 'class' => '', 'text' => '4', 'date' => '2016-06-04', ], 113 | ], 114 | 115 | [ 116 | [ 'class' => '', 'text' => '5', 'date' => '2016-06-05', ], 117 | [ 'class' => '', 'text' => '6', 'date' => '2016-06-06', ], 118 | [ 'class' => '', 'text' => '7', 'date' => '2016-06-07', ], 119 | [ 'class' => '', 'text' => '8', 'date' => '2016-06-08', ], 120 | [ 'class' => '', 'text' => '9', 'date' => '2016-06-09', ], 121 | [ 'class' => '', 'text' => '10', 'date' => '2016-06-10', ], 122 | [ 'class' => '', 'text' => '11', 'date' => '2016-06-11', ], 123 | ], 124 | 125 | [ 126 | [ 'class' => '', 'text' => '12', 'date' => '2016-06-12', ], 127 | [ 'class' => '', 'text' => '13', 'date' => '2016-06-13', ], 128 | [ 'class' => '', 'text' => '14', 'date' => '2016-06-14', ], 129 | [ 'class' => '', 'text' => '15', 'date' => '2016-06-15', ], 130 | [ 'class' => '', 'text' => '16', 'date' => '2016-06-16', ], 131 | [ 'class' => '', 'text' => '17', 'date' => '2016-06-17', ], 132 | [ 'class' => '', 'text' => '18', 'date' => '2016-06-18', ], 133 | ], 134 | 135 | [ 136 | [ 'class' => '', 'text' => '19', 'date' => '2016-06-19', ], 137 | [ 'class' => '', 'text' => '20', 'date' => '2016-06-20', ], 138 | [ 'class' => '', 'text' => '21', 'date' => '2016-06-21', ], 139 | [ 'class' => '', 'text' => '22', 'date' => '2016-06-22', ], 140 | [ 'class' => '', 'text' => '23', 'date' => '2016-06-23', ], 141 | [ 'class' => '', 'text' => '24', 'date' => '2016-06-24', ], 142 | [ 'class' => '', 'text' => '25', 'date' => '2016-06-25', ], 143 | ], 144 | 145 | [ 146 | [ 'class' => '', 'text' => '26', 'date' => '2016-06-26', ], 147 | [ 'class' => '', 'text' => '27', 'date' => '2016-06-27', ], 148 | [ 'class' => '', 'text' => '28', 'date' => '2016-06-28', ], 149 | [ 'class' => '', 'text' => '29', 'date' => '2016-06-29', ], 150 | [ 'class' => '', 'text' => '30', 'date' => '2016-06-30', ], 151 | [ 'class' => 'SCsuffix', 'text' => ' ', ], 152 | [ 'class' => 'SCsuffix', 'text' => ' ', ], 153 | ], 154 | ]; 155 | 156 | $this->assertSame($expected, $tableArray); 157 | } 158 | 159 | public function testGenericGeneration_mTs() : void { 160 | $cal = new SimpleCalendar("June 5 2016"); 161 | $cal->setStartOfWeek(5); 162 | 163 | $tableArray = $this->parseCalendarHtml($cal); 164 | 165 | $expected = [ 166 | [ 167 | [ 'class' => '', 'text' => 'Fri', ], 168 | [ 'class' => '', 'text' => 'Sat', ], 169 | [ 'class' => '', 'text' => 'Sun', ], 170 | [ 'class' => '', 'text' => 'Mon', ], 171 | [ 'class' => '', 'text' => 'Tue', ], 172 | [ 'class' => '', 'text' => 'Wed', ], 173 | [ 'class' => '', 'text' => 'Thu', ], 174 | ], 175 | [ 176 | [ 'class' => 'SCprefix', 'text' => ' ', ], 177 | [ 'class' => 'SCprefix', 'text' => ' ', ], 178 | [ 'class' => 'SCprefix', 'text' => ' ', ], 179 | [ 'class' => 'SCprefix', 'text' => ' ', ], 180 | [ 'class' => 'SCprefix', 'text' => ' ', ], 181 | [ 'class' => '', 'text' => '1', 'date' => '2016-06-01', ], 182 | [ 'class' => '', 'text' => '2', 'date' => '2016-06-02', ], 183 | ], 184 | [ 185 | [ 'class' => '', 'text' => '3', 'date' => '2016-06-03', ], 186 | [ 'class' => '', 'text' => '4', 'date' => '2016-06-04', ], 187 | [ 'class' => '', 'text' => '5', 'date' => '2016-06-05', ], 188 | [ 'class' => '', 'text' => '6', 'date' => '2016-06-06', ], 189 | [ 'class' => '', 'text' => '7', 'date' => '2016-06-07', ], 190 | [ 'class' => '', 'text' => '8', 'date' => '2016-06-08', ], 191 | [ 'class' => '', 'text' => '9', 'date' => '2016-06-09', ], 192 | ], 193 | [ 194 | [ 'class' => '', 'text' => '10', 'date' => '2016-06-10', ], 195 | [ 'class' => '', 'text' => '11', 'date' => '2016-06-11', ], 196 | [ 'class' => '', 'text' => '12', 'date' => '2016-06-12', ], 197 | [ 'class' => '', 'text' => '13', 'date' => '2016-06-13', ], 198 | [ 'class' => '', 'text' => '14', 'date' => '2016-06-14', ], 199 | [ 'class' => '', 'text' => '15', 'date' => '2016-06-15', ], 200 | [ 'class' => '', 'text' => '16', 'date' => '2016-06-16', ], 201 | ], 202 | [ 203 | [ 'class' => '', 'text' => '17', 'date' => '2016-06-17', ], 204 | [ 'class' => '', 'text' => '18', 'date' => '2016-06-18', ], 205 | [ 'class' => '', 'text' => '19', 'date' => '2016-06-19', ], 206 | [ 'class' => '', 'text' => '20', 'date' => '2016-06-20', ], 207 | [ 'class' => '', 'text' => '21', 'date' => '2016-06-21', ], 208 | [ 'class' => '', 'text' => '22', 'date' => '2016-06-22', ], 209 | [ 'class' => '', 'text' => '23', 'date' => '2016-06-23', ], 210 | ], 211 | [ 212 | [ 'class' => '', 'text' => '24', 'date' => '2016-06-24', ], 213 | [ 'class' => '', 'text' => '25', 'date' => '2016-06-25', ], 214 | [ 'class' => '', 'text' => '26', 'date' => '2016-06-26', ], 215 | [ 'class' => '', 'text' => '27', 'date' => '2016-06-27', ], 216 | [ 'class' => '', 'text' => '28', 'date' => '2016-06-28', ], 217 | [ 'class' => '', 'text' => '29', 'date' => '2016-06-29', ], 218 | [ 'class' => '', 'text' => '30', 'date' => '2016-06-30', ], 219 | ], 220 | ]; 221 | 222 | $this->assertSame($expected, $tableArray); 223 | } 224 | 225 | /** 226 | * @return array>> 227 | */ 228 | private function parseCalendarHtml( SimpleCalendar $cal ) : array { 229 | $x = new DOMDocument; 230 | @$x->loadHTML($cal->show(false)); 231 | 232 | $trs = $x->getElementsByTagName('tr'); 233 | $tableArray = []; 234 | $rowi = 0; 235 | foreach( $trs as $tr ) { 236 | /** 237 | * @var \DOMElement $tr 238 | */ 239 | $this->assertEquals(7, $tr->childNodes->length); 240 | 241 | $rowArray = []; 242 | foreach( $tr->childNodes as $childNode ) { 243 | /** 244 | * @var \DOMElement $childNode 245 | */ 246 | $class = $childNode->getAttribute("class"); 247 | $rowItem = [ 248 | 'class' => $class, 249 | 'text' => $childNode->textContent, 250 | ]; 251 | 252 | if( $rowi == 0 ) { 253 | $this->assertSame('th', $childNode->tagName); 254 | } else { 255 | $this->assertSame('td', $childNode->tagName); 256 | 257 | $time = $childNode->getElementsByTagName('time'); 258 | 259 | if( $class === 'SCprefix' || $class === 'SCsuffix' ) { 260 | $this->assertSame(0, $time->length); 261 | } else { 262 | $this->assertGreaterThan(0, $time->length); 263 | $item = $time->item(0); 264 | assert($item instanceof DOMElement); 265 | $rowItem['date'] = $item->getAttribute('datetime'); 266 | } 267 | } 268 | 269 | $rowArray[] = $rowItem; 270 | } 271 | 272 | $tableArray[] = $rowArray; 273 | 274 | $rowi++; 275 | } 276 | 277 | return $tableArray; 278 | } 279 | 280 | } 281 | --------------------------------------------------------------------------------