Repository: qax-os/excelize Branch: master Commit: df428826c02b Files: 115 Total size: 3.4 MB Directory structure: gitextract_jqjalinp/ ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── SECURITY.md │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── adjust.go ├── adjust_test.go ├── calc.go ├── calc_test.go ├── calcchain.go ├── calcchain_test.go ├── cell.go ├── cell_test.go ├── chart.go ├── chart_test.go ├── col.go ├── col_test.go ├── crypt.go ├── crypt_test.go ├── datavalidation.go ├── datavalidation_test.go ├── date.go ├── date_test.go ├── docProps.go ├── docProps_test.go ├── drawing.go ├── drawing_test.go ├── errors.go ├── errors_test.go ├── excelize.go ├── excelize_test.go ├── file.go ├── file_test.go ├── go.mod ├── go.sum ├── hsl.go ├── lib.go ├── lib_test.go ├── merge.go ├── merge_test.go ├── numfmt.go ├── numfmt_test.go ├── picture.go ├── picture_test.go ├── pivotTable.go ├── pivotTable_test.go ├── rows.go ├── rows_test.go ├── shape.go ├── shape_test.go ├── sheet.go ├── sheet_test.go ├── sheetpr.go ├── sheetpr_test.go ├── sheetview.go ├── sheetview_test.go ├── slicer.go ├── slicer_test.go ├── sparkline.go ├── sparkline_test.go ├── stream.go ├── stream_test.go ├── styles.go ├── styles_test.go ├── table.go ├── table_test.go ├── templates.go ├── test/ │ ├── BadWorkbook.xlsx │ ├── Book1.xlsx │ ├── CalcChain.xlsx │ ├── MergeCell.xlsx │ ├── OverflowNumericCell.xlsx │ ├── SharedStrings.xlsx │ ├── encryptAES.xlsx │ ├── encryptSHA1.xlsx │ └── images/ │ ├── excel.emf │ ├── excel.emz │ ├── excel.tif │ ├── excel.wmf │ └── excel.wmz ├── vml.go ├── vmlDrawing.go ├── vml_test.go ├── workbook.go ├── workbook_test.go ├── xmlApp.go ├── xmlCalcChain.go ├── xmlChart.go ├── xmlChartSheet.go ├── xmlComments.go ├── xmlContentTypes.go ├── xmlCore.go ├── xmlCustom.go ├── xmlDecodeDrawing.go ├── xmlDrawing.go ├── xmlMetaData.go ├── xmlPivotCache.go ├── xmlPivotTable.go ├── xmlSharedStrings.go ├── xmlSlicers.go ├── xmlStyles.go ├── xmlTable.go ├── xmlTheme.go ├── xmlWorkbook.go └── xmlWorksheet.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others’ private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [xuri.me](https://xuri.me). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning Community Impact: A violation through a single incident or series of actions. Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban Community Impact: A serious violation of community standards, including sustained inappropriate behavior. Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. Consequence: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to excelize Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and guidelines useful to experienced open source contributors. Finally, make sure you read our [community guidelines](#community-guidelines) before you start participating. ## Topics * [Reporting Security Issues](#reporting-security-issues) * [Design and Cleanup Proposals](#design-and-cleanup-proposals) * [Reporting Issues](#reporting-other-issues) * [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines) * [Community Guidelines](#community-guidelines) ## Reporting security issues The excelize maintainers take security seriously. If you discover a security issue, please bring it to their attention right away! Please **DO NOT** file a public issue, instead send your report privately to [xuri.me](https://xuri.me). Security reports are greatly appreciated and we will publicly thank you for them. We currently do not offer a paid security bounty program, but are not ruling it out in the future. ## Reporting other issues A great way to contribute to the project is to send a detailed report when you encounter an issue. We always appreciate a well-written, thorough bug report, and will thank you for it! Check that [our issue database](https://github.com/xuri/excelize/issues) doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates. Do *not* leave random "+1" or "I have this too" comments, as they only clutter the discussion, and don't help resolving it. However, if you have ways to reproduce the issue or have additional information that may help resolving the issue, please leave a comment. When reporting issues, always include the output of `go env`. Also include the steps required to reproduce the problem if possible and applicable. This information will help us review and fix your issue faster. When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com). Don't forget to remove sensitive data from your logfiles before posting (you can replace those parts with "REDACTED"). ## Quick contribution tips and guidelines This section gives the experienced contributor some tips and guidelines. ### Pull requests are always welcome Not sure if that typo is worth a pull request? Found a bug and know how to fix it? Do it! We will appreciate it. Any significant improvement should be documented as [a GitHub issue](https://github.com/xuri/excelize/issues) before anybody starts working on it. We are always thrilled to receive pull requests. We do our best to process them quickly. If your pull request is not accepted on the first try, don't get discouraged! ### Design and cleanup proposals You can propose new designs for existing excelize features. You can also design entirely new features. We really appreciate contributors who want to refactor or otherwise cleanup our project. We try hard to keep excelize lean and focused. Excelize can't do everything for everybody. This means that we might decide against incorporating a new feature. However, there might be a way to implement that feature *on top of* excelize. ### Conventions Fork the repository and make changes on your fork in a feature branch: * If it's a bug fix branch, name it XXXX-something where XXXX is the number of the issue. * If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXXX-something where XXXX is the number of the issue. Submit unit tests for your changes. Go has a great test framework built in; use it! Take a look at existing tests for inspiration. Run the full test on your branch before submitting a pull request. Update the documentation when creating or modifying features. Test your documentation changes for clarity, concision, and correctness, as well as a clean documentation build. Write clean code. Universally formatted code promotes ease of writing, reading, and maintenance. Always run `gofmt -s -w file.go` on each changed file before committing your changes. Most editors have plug-ins that do this automatically. Pull request descriptions should be as clear as possible and include a reference to all the issues that they address. ### Successful Changes Before contributing large or high impact changes, make the effort to coordinate with the maintainers of the project before submitting a pull request. This prevents you from doing extra work that may or may not be merged. Large PRs that are just submitted without any prior communication is unlikely to be successful. While pull requests are the methodology for submitting changes to code, changes are much more likely to be accepted if they are accompanied by additional engineering work. While we don't define this explicitly, most of these goals are accomplished through the communication of the design goals and subsequent solutions. Oftentimes, it helps to first state the problem before presenting solutions. Typically, the best methods of accomplishing this are to submit an issue, stating the problem. This issue can include a problem statement and a checklist with requirements. If solutions are proposed, alternatives should be listed and eliminated. Even if the criteria for elimination of a solution is frivolous, say so. Larger changes typically work best with design documents. These are focused on providing context to the design at the time the feature was conceived and can inform future documentation contributions. ### Commit Messages Commit messages must start with a capitalized and short summary written in the imperative, followed by an optional, more detailed explanatory text which is separated from the summary by an empty line. Commit messages should follow best practices, including explaining the context of the problem and how it was solved, including in caveats or follow-up changes required. They should tell the story of the change and provide readers understanding of what led to it. In practice, the best approach to maintaining a nice commit message is to leverage a `git add -p` and `git commit --amend` to formulate a solid changeset. This allows one to piece together a change, as information becomes available. If you squash a series of commits, don't just submit that. Re-write the commit message, as if the series of commits was a single stroke of brilliance. That said, there is no requirement to have a single commit for a PR, as long as each commit tells the story. For example, if there is a feature that requires a package, it might make sense to have the package in a separate commit then have a subsequent commit that uses it. Remember, you're telling part of the story with the commit message. Don't make your chapter weird. ### Review Code review comments may be added to your pull request. Discuss, then make the suggested modifications and push additional commits to your feature branch. Post a comment after pushing. New commits show up in the pull request automatically, but the reviewers are notified only when you comment. Pull requests must be cleanly rebased on top of master without multiple branches mixed into the PR. **Git tip**: If your PR no longer merges cleanly, use `rebase master` in your feature branch to update your pull request rather than `merge master`. Before you make a pull request, squash your commits into logical units of work using `git rebase -i` and `git push -f`. A logical unit of work is a consistent set of patches that should be reviewed together: for example, upgrading the version of a vendored dependency and taking advantage of its now available new feature constitute two separate units of work. Implementing a new function and calling it in another file constitute a single logical unit of work. The very high majority of submissions should have a single commit, so if in doubt: squash down to one. After every commit, make sure the test passes. Include documentation changes in the same pull request so that a revert would remove all traces of the feature or fix. Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that close an issue. Including references automatically closes the issue on a merge. Please see the [Coding Style](#coding-style) for further guidelines. ### Merge approval The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to indicate acceptance. ### Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](https://developercertificate.org)): ```text Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: ```text Signed-off-by: Ri Xu https://xuri.me ``` Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. ### How can I become a maintainer First, all maintainers have 3 things * They share responsibility in the project's success. * They have made a long-term, recurring time investment to improve the project. * They spend that time doing whatever needs to be done, not necessarily what is the most interesting or fun. Maintainers are often under-appreciated, because their work is harder to appreciate. It's easy to appreciate a really cool and technically advanced feature. It's harder to appreciate the absence of bugs, the slow but steady improvement in stability, or the reliability of a release process. But those things distinguish a good project from a great one. Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available. You don't have to be a maintainer to make a difference on the project! If you want to become a maintainer, contact [xuri.me](https://xuri.me) and given an introduction of you. ## Community guidelines We want to keep the community awesome, growing and collaborative. We need your help to keep it that way. To help with this we've come up with some general guidelines for the community as a whole: * Be nice: Be courteous, respectful and polite to fellow community members: no regional, racial, gender, or other abuse will be tolerated. We like nice people way better than mean ones! * Encourage diversity and participation: Make everyone in our community feel welcome, regardless of their background and the extent of their contributions, and do everything possible to encourage participation in our community. * Keep it legal: Basically, don't get us in trouble. Share only content that you own, do not share private or sensitive information, and don't break the law. * Stay on topic: Make sure that you are posting to the correct channel and avoid off-topic discussions. Remember when you update an issue or respond to an email you are potentially sending to a large number of people. Please consider this before you update. Also remember that nobody likes spam. * Don't send email to the maintainers: There's no need to send email to the maintainers to ask them to investigate an issue or to take a look at a pull request. Instead of sending an email, GitHub mentions should be used to ping maintainers to review a pull request, a proposal or an issue. ### Guideline violations — 3 strikes method The point of this section is not to find opportunities to punish people, but we do need a fair way to deal with people who are making our community suck. 1. First occurrence: We'll give you a friendly, but public reminder that the behavior is inappropriate according to our guidelines. 2. Second occurrence: We will send you a private message with a warning that any additional violations will result in removal from the community. 3. Third occurrence: Depending on the violation, we may need to delete or ban your account. **Notes:** * Obvious spammers are banned on first occurrence. If we don't do this, we'll have spam all over the place. * Violations are forgiven after 6 months of good behavior, and we won't hold a grudge. * People who commit minor infractions will get some education, rather than hammering them in the 3 strikes process. * The rules apply equally to everyone in the community, no matter how much you've contributed. * Extreme violations of a threatening, abusive, destructive or illegal nature will be addressed immediately and are not subject to 3 strikes or forgiveness. * Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of appeals, we know that mistakes happen, and we'll work with you to come up with a fair solution if there has been a misunderstanding. ## Coding Style Unless explicitly stated, we follow all coding guidelines from the Go community. While some of these standards may seem arbitrary, they somehow seem to result in a solid, consistent codebase. It is possible that the code base does not currently comply with these guidelines. We are not looking for a massive PR that fixes this, since that goes against the spirit of the guidelines. All new contributions should make a best effort to clean up and make the code base better than they left it. Obviously, apply your best judgement. Remember, the goal here is to make the code base easier for humans to navigate and understand. Always keep that in mind when nudging others to comply. The rules: 1. All code should be formatted with `gofmt -s`. 2. All code should pass the default levels of [`go vet`](https://pkg.go.dev/cmd/vet). 3. All code should follow the guidelines covered in [Effective Go](https://go.dev/doc/effective_go) and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). 4. Comment the code. Tell us the why, the history and the context. 5. Document _all_ declarations and methods, even private ones. Declare expectations, caveats and anything else that may be important. If a type gets exported, having the comments already there will ensure it's ready. 6. Variable name length should be proportional to its context and no longer. `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`. In practice, short methods will have short variable names and globals will have longer names. 7. No underscores in package names. If you need a compound name, step back, and re-examine why you need a compound name. If you still think you need a compound name, lose the underscore. 8. No utils or helpers packages. If a function is not general enough to warrant its own package, it has not been written generally enough to be a part of a util package. Just leave it unexported and well-documented. 9. All tests should run with `go test` and outside tooling should not be required. No, we don't need another unit testing framework. Assertion packages are acceptable if they provide _real_ incremental value. 10. Even though we call these "rules" above, they are actually just guidelines. Since you've read all the rules, you now know that. If you are having trouble getting into the mood of idiomatic Go, we recommend reading through [Effective Go](https://go.dev/doc/effective_go). The [Go Blog](https://go.dev/blog/) is also a great resource. Drinking the kool-aid is a lot easier than going thirsty. ## Code Review Comments and Effective Go Guidelines [CodeLingo](https://www.codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://go.dev/doc/effective_go) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). ### Package Comment Every package should have a package comment, a block comment preceding the package clause. For multi-file packages, the package comment only needs to be present in one file, and any one will do. The package comment should introduce the package and provide information relevant to the package as a whole. It will appear first on the godoc page and should set up the detailed documentation that follows. ### Single Method Interface Name By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc. There are a number of such names and it's productive to honor them and the function names they capture. Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion, don't give your method one of those names unless it has the same signature and meaning. Conversely, if your type implements a method with the same meaning as a method on a well-known type, give it the same name and signature; call your string-converter method String not ToString. ### Avoid Annotations in Comments Comments do not need extra formatting such as banners of stars. The generated output may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc, like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc does do is to display indented text in a fixed-width font, suitable for program snippets. The package comment for the fmt package uses this to good effect. ### Comment First Word as Subject Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared. ### Good Package Name It's helpful if everyone using the package can use the same name to refer to its contents, which implies that the package name should be good: short, concise, and evocative. By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps. Err on the side of brevity, since everyone using your package will be typing that name. And don't worry about collisions a priori. The package name is only the default name for imports; it need not be unique across all source code, and in the rare case of a collision the importing package can choose a different name to use locally. In any case, confusion is rare because the file name in the import determines just which package is being used. ### Avoid Renaming Imports Avoid renaming imports except to avoid a name collision; good package names should not require renaming. In the event of collision, prefer to rename the most local or project-specific import. ### Context as First Argument Values of the context.Context type carry security credentials, tracing information, deadlines, and cancellation signals across API and process boundaries. Go programs pass Contexts explicitly along the entire function call chain from incoming RPCs and HTTP requests to outgoing requests. Most functions that use a Context should accept it as their first parameter. ### Do Not Discard Errors Do not discard errors using _ variables. If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic. ### Go Error Format Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so that log.Printf("Reading %s: %v", filename, err) formats without a spurious capital letter mid-message. This does not apply to logging, which is implicitly line-oriented and not combined inside other messages. ### Use Crypto Rand Do not use package math/rand to generate keys, even throwaway ones. Unseeded, the generator is completely predictable. Seeded with time.Nanoseconds(), there are just a few bits of entropy. Instead, use crypto/rand's Reader, and if you need text, print to hexadecimal or base64. ================================================ FILE: .github/FUNDING.yml ================================================ github: xuri open_collective: excelize patreon: xuri ko_fi: xurime liberapay: xuri issuehunt: xuri custom: https://www.paypal.com/paypalme/xuri ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: Create a report to help us improve body: - type: markdown attributes: value: | If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead. - type: textarea id: description attributes: label: Description description: Briefly describe the problem you are having in a few paragraphs. validations: required: true - type: textarea id: reproduction-steps attributes: label: Steps to reproduce the issue description: Explain how to cause the issue in the provided reproduction. placeholder: | 1. 2. 3. validations: required: true - type: textarea id: received attributes: label: Describe the results you received validations: required: true - type: textarea id: expected attributes: label: Describe the results you expected validations: required: true - type: input id: go-version attributes: label: Go version description: | Output of `go version`: placeholder: e.g. 1.23.4 validations: required: true - type: input id: excelize-version attributes: label: Excelize version or commit ID description: | Which version of Excelize are you using? placeholder: e.g. 2.9.0 validations: required: true - type: textarea id: env attributes: label: Environment description: Environment details (OS, Microsoft Excel™ version, physical, etc.) render: shell validations: required: true - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate. required: true - label: The provided reproduction is a minimal reproducible example of the bug. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature request description: Suggest an idea for this project body: - type: markdown attributes: value: | If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead. - type: textarea id: description attributes: label: Description description: Describe the feature that you would like added validations: required: true - type: textarea id: additional-context attributes: label: Additional context description: Any other context or screenshots about the feature request here? - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Check that there isn't already an issue that requests the same feature to avoid creating a duplicate. required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ # PR Details ## Description ## Related Issue ## Motivation and Context ## How Has This Been Tested ## Types of changes - [ ] Docs change / refactoring / dependency upgrade - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have read the **CONTRIBUTING** document. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy ## Supported Versions We will dive into any security-related issue as long as your Excelize version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information. ## Reporting a Vulnerability Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you! ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [master] pull_request: branches: [master] schedule: - cron: '0 6 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: language: ['go'] steps: - name: Checkout repository uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Autobuild uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/go.yml ================================================ on: [push, pull_request] name: build jobs: test: strategy: matrix: go-version: [1.25.x, 1.26.x] os: [ubuntu-24.04, macos-15-intel, macos-26, windows-latest] targetplatform: [x86, x64] runs-on: ${{ matrix.os }} steps: - name: Install Go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} cache: false - name: Checkout code uses: actions/checkout@v6 - name: Get dependencies run: | env GO111MODULE=on go vet ./... - name: Build run: go build -v . - name: Build on ARM if: runner.os == 'Linux' run: | GOARCH=arm GOARM=5 go build . GOARCH=arm GOARM=6 go build . GOARCH=arm GOARM=7 go build . GOARCH=arm64 go build . GOARCH=arm64 GOOS=android go build . - name: Test run: env GO111MODULE=on go test -v -timeout 60m -race ./... -coverprofile='coverage.txt' -covermode=atomic - name: Codecov uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: coverage.txt flags: unittests name: codecov-umbrella ================================================ FILE: .gitignore ================================================ .DS_Store .idea *.json *.out *.test ~$*.xlsx test/*.png test/BadWorkbook.SaveAsEmptyStruct.xlsx test/Encryption*.xlsx test/excelize-* test/Test*.xlam test/Test*.xlsm test/Test*.xlsx test/Test*.xltm test/Test*.xltx ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2016-2026 The excelize Authors. Copyright (c) 2011-2017 Geoffrey J. Teale All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================

Excelize logo

Build Status Code Coverage Go Report Card go.dev Licenses Donate

# Excelize ## Introduction Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.25.0 or later. The full docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/). ## Basic Usage ### Installation ```bash go get github.com/xuri/excelize ``` - If your packages are managed using [Go Modules](https://go.dev/blog/using-go-modules), please install with following command. ```bash go get github.com/xuri/excelize/v2 ``` ### Create spreadsheet Here is a minimal example usage that will create spreadsheet file. ```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() // Create a new sheet. index, err := f.NewSheet("Sheet2") if err != nil { fmt.Println(err) return } // Set value of a cell. f.SetCellValue("Sheet2", "A2", "Hello world.") f.SetCellValue("Sheet1", "B2", 100) // Set active sheet of the workbook. f.SetActiveSheet(index) // Save spreadsheet by the given path. if err := f.SaveAs("Book1.xlsx"); err != nil { fmt.Println(err) } } ``` ### Reading spreadsheet The following constitutes the bare to read a spreadsheet document. ```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f, err := excelize.OpenFile("Book1.xlsx") if err != nil { fmt.Println(err) return } defer func() { // Close the spreadsheet. if err := f.Close(); err != nil { fmt.Println(err) } }() // Get value from cell by given worksheet name and cell reference. cell, err := f.GetCellValue("Sheet1", "B2") if err != nil { fmt.Println(err) return } fmt.Println(cell) // Get all the rows in the Sheet1. rows, err := f.GetRows("Sheet1") if err != nil { fmt.Println(err) return } for _, row := range rows { for _, colCell := range row { fmt.Print(colCell, "\t") } fmt.Println() } } ``` ### Add chart to spreadsheet file With Excelize chart generation and management is as easy as a few lines of code. You can build charts based on data in your worksheet or generate charts without any data in your worksheet at all.

Excelize

```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() for idx, row := range [][]interface{}{ {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, } { cell, err := excelize.CoordinatesToCellName(1, idx+1) if err != nil { fmt.Println(err) return } f.SetSheetRow("Sheet1", cell, &row) } if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ Type: excelize.Col3DClustered, Series: []excelize.ChartSeries{ { Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2", }, { Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3", }, { Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4", }}, Title: []excelize.RichTextRun{ { Text: "Fruit 3D Clustered Column Chart", }, }, }); err != nil { fmt.Println(err) return } // Save spreadsheet by the given path. if err := f.SaveAs("Book1.xlsx"); err != nil { fmt.Println(err) } } ``` ### Add picture to spreadsheet file ```go package main import ( "fmt" _ "image/gif" _ "image/jpeg" _ "image/png" "github.com/xuri/excelize/v2" ) func main() { f, err := excelize.OpenFile("Book1.xlsx") if err != nil { fmt.Println(err) return } defer func() { // Close the spreadsheet. if err := f.Close(); err != nil { fmt.Println(err) } }() // Insert a picture. if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil { fmt.Println(err) } // Insert a picture to worksheet with scaling. if err := f.AddPicture("Sheet1", "D2", "image.jpg", &excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil { fmt.Println(err) } // Insert a picture offset in the cell with printing support. enable, disable := true, false if err := f.AddPicture("Sheet1", "H2", "image.gif", &excelize.GraphicOptions{ PrintObject: &enable, LockAspectRatio: false, OffsetX: 15, OffsetY: 10, Locked: &disable, }); err != nil { fmt.Println(err) } // Save the spreadsheet with the origin path. if err = f.Save(); err != nil { fmt.Println(err) } } ``` ## Contributing Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/). ## Licenses This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause). The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation. The Go gopher was created by [Renee French](https://go.dev/doc/gopher/README). Licensed under the [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/). ## Gold Sponsors Gravity ================================================ FILE: README_zh.md ================================================

Excelize logo

Build Status Code Coverage Go Report Card go.dev Licenses Donate

# Excelize ## 简介 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写函数,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.25.0 或更高版本。完整的使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)。 ## 快速上手 ### 安装 ```bash go get github.com/xuri/excelize ``` - 如果您使用 [Go Modules](https://go.dev/blog/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。 ```bash go get github.com/xuri/excelize/v2 ``` ### 创建 Excel 文档 下面是一个创建 Excel 文档的简单例子: ```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() // 创建一个工作表 index, err := f.NewSheet("Sheet2") if err != nil { fmt.Println(err) return } // 设置单元格的值 f.SetCellValue("Sheet2", "A2", "Hello world.") f.SetCellValue("Sheet1", "B2", 100) // 设置工作簿的默认工作表 f.SetActiveSheet(index) // 根据指定路径保存文件 if err := f.SaveAs("Book1.xlsx"); err != nil { fmt.Println(err) } } ``` ### 读取 Excel 文档 下面是读取 Excel 文档的例子: ```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f, err := excelize.OpenFile("Book1.xlsx") if err != nil { fmt.Println(err) return } defer func() { // 关闭工作簿 if err := f.Close(); err != nil { fmt.Println(err) } }() // 获取工作表中指定单元格的值 cell, err := f.GetCellValue("Sheet1", "B2") if err != nil { fmt.Println(err) return } fmt.Println(cell) // 获取 Sheet1 上所有单元格 rows, err := f.GetRows("Sheet1") if err != nil { fmt.Println(err) return } for _, row := range rows { for _, colCell := range row { fmt.Print(colCell, "\t") } fmt.Println() } } ``` ### 在 Excel 文档中创建图表 使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。

使用 Excelize 在 Excel 电子表格文档中创建图表

```go package main import ( "fmt" "github.com/xuri/excelize/v2" ) func main() { f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() for idx, row := range [][]interface{}{ {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, } { cell, err := excelize.CoordinatesToCellName(1, idx+1) if err != nil { fmt.Println(err) return } f.SetSheetRow("Sheet1", cell, &row) } if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ Type: excelize.Col3DClustered, Series: []excelize.ChartSeries{ { Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2", }, { Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3", }, { Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4", }}, Title: []excelize.RichTextRun{ { Text: "Fruit 3D Clustered Column Chart", }, }, }); err != nil { fmt.Println(err) return } // 根据指定路径保存文件 if err := f.SaveAs("Book1.xlsx"); err != nil { fmt.Println(err) } } ``` ### 向 Excel 文档中插入图片 ```go package main import ( "fmt" _ "image/gif" _ "image/jpeg" _ "image/png" "github.com/xuri/excelize/v2" ) func main() { f, err := excelize.OpenFile("Book1.xlsx") if err != nil { fmt.Println(err) return } defer func() { // 关闭工作簿 if err := f.Close(); err != nil { fmt.Println(err) } }() // 插入图片 if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil { fmt.Println(err) } // 在工作表中插入图片,并设置图片的缩放比例 if err := f.AddPicture("Sheet1", "D2", "image.jpg", &excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil { fmt.Println(err) } // 在工作表中插入图片,并设置图片的打印属性 enable, disable := true, false if err := f.AddPicture("Sheet1", "H2", "image.gif", &excelize.GraphicOptions{ PrintObject: &enable, LockAspectRatio: false, OffsetX: 15, OffsetY: 10, Locked: &disable, }); err != nil { fmt.Println(err) } // 保存工作簿 if err = f.Save(); err != nil { fmt.Println(err) } } ``` ## 社区合作 欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)。 ## 开源许可 本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。 Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。 Go gopher 由 [Renee French](https://go.dev/doc/gopher/README) 创作,遵循 [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/) 创作共用授权条款。 ## 金牌赞助商 Gravity ================================================ FILE: adjust.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" "strconv" "strings" "unicode" "github.com/xuri/efp" ) type adjustDirection bool const ( columns adjustDirection = false rows adjustDirection = true ) // adjustHelperFunc defines functions to adjust helper. var adjustHelperFunc = [9]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{ func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustConditionalFormats(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustDataValidations(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustDefinedNames(sheet, dir, num, offset) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustDrawings(ws, sheet, dir, num, offset) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustAutoFilter(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustTable(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID) }, } // adjustHelper provides a function to adjust rows and columns dimensions, // hyperlinks, merged cells and auto filter when inserting or deleting rows or // columns. // // sheet: Worksheet name that we're editing // column: Index number of the column we're inserting/deleting before // row: Index number of the row we're inserting/deleting before // offset: Number of rows/column to insert/delete negative values indicate deletion // // TODO: adjustComments, adjustPageBreaks, adjustProtectedCells func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } f.clearCalcCache() sheetID := f.getSheetID(sheet) if dir == rows { err = f.adjustRowDimensions(sheet, ws, num, offset) } else { err = f.adjustColDimensions(sheet, ws, num, offset) } if err != nil { return err } f.adjustHyperlinks(ws, sheet, dir, num, offset) ws.checkSheet() _ = ws.checkRow() for _, fn := range adjustHelperFunc { if err := fn(f, ws, sheet, dir, num, offset, sheetID); err != nil { return err } } if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 { ws.MergeCells = nil } return nil } // adjustCols provides a function to update column style when inserting or // deleting columns. func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error { if ws.Cols == nil { return nil } for i := 0; i < len(ws.Cols.Col); i++ { if offset > 0 { if ws.Cols.Col[i].Min >= col { if ws.Cols.Col[i].Min += offset; ws.Cols.Col[i].Min > MaxColumns { ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...) i-- continue } } if ws.Cols.Col[i].Max >= col || ws.Cols.Col[i].Max+1 == col { if ws.Cols.Col[i].Max += offset; ws.Cols.Col[i].Max > MaxColumns { ws.Cols.Col[i].Max = MaxColumns } } continue } if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col { ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...) i-- continue } if ws.Cols.Col[i].Min > col { ws.Cols.Col[i].Min += offset } if ws.Cols.Col[i].Max >= col { ws.Cols.Col[i].Max += offset } } if len(ws.Cols.Col) == 0 { ws.Cols = nil } return nil } // adjustColDimensions provides a function to update column dimensions when // inserting or deleting rows or columns. func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset int) error { for rowIdx := range ws.SheetData.Row { for _, v := range ws.SheetData.Row[rowIdx].C { if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol { if newCol := cellCol + offset; newCol > 0 && newCol > MaxColumns { return ErrColumnNumber } } } } for _, sheetN := range f.GetSheetList() { worksheet, err := f.workSheetReader(sheetN) if err != nil { if err.Error() == newNotWorksheetError(sheetN).Error() { continue } return err } for rowIdx := range worksheet.SheetData.Row { for colIdx, v := range worksheet.SheetData.Row[rowIdx].C { if cellCol, cellRow, _ := CellNameToCoordinates(v.R); sheetN == sheet && col <= cellCol { if newCol := cellCol + offset; newCol > 0 { worksheet.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow) } } if err := f.adjustFormula(sheet, sheetN, &worksheet.SheetData.Row[rowIdx].C[colIdx], columns, col, offset, false); err != nil { return err } } } } return f.adjustCols(ws, col, offset) } // adjustRowDimensions provides a function to update row dimensions when // inserting or deleting rows or columns. func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset int) error { for _, sheetN := range f.GetSheetList() { if sheetN == sheet { continue } worksheet, err := f.workSheetReader(sheetN) if err != nil { if err.Error() == newNotWorksheetError(sheetN).Error() { continue } return err } numOfRows := len(worksheet.SheetData.Row) for i := 0; i < numOfRows; i++ { r := &worksheet.SheetData.Row[i] if err = f.adjustSingleRowFormulas(sheet, sheetN, r, row, offset, false); err != nil { return err } } } totalRows := len(ws.SheetData.Row) if totalRows == 0 { return nil } lastRow := &ws.SheetData.Row[totalRows-1] if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow > TotalRows { return ErrMaxRows } numOfRows := len(ws.SheetData.Row) for i := 0; i < numOfRows; i++ { r := &ws.SheetData.Row[i] if newRow := r.R + offset; r.R >= row && newRow > 0 { r.adjustSingleRowDimensions(offset) } if err := f.adjustSingleRowFormulas(sheet, sheet, r, row, offset, false); err != nil { return err } } return nil } // adjustSingleRowDimensions provides a function to adjust single row dimensions. func (r *xlsxRow) adjustSingleRowDimensions(offset int) { r.R += offset for i, col := range r.C { colName, _, _ := SplitCellName(col.R) r.C[i].R, _ = JoinCellName(colName, r.R) } } // adjustSingleRowFormulas provides a function to adjust single row formulas. func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, offset int, si bool) error { for i := 0; i < len(r.C); i++ { if err := f.adjustFormula(sheet, sheetN, &r.C[i], rows, num, offset, si); err != nil { return err } } return nil } // adjustCellRef provides a function to adjust cell reference. func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset int) (string, error) { var SQRef []string applyOffset := func(coordinates []int, idx1, idx2, maxVal int) []int { if coordinates[idx1] >= num { coordinates[idx1] += offset } if coordinates[idx2] >= num { if coordinates[idx2] += offset; coordinates[idx2] > maxVal { coordinates[idx2] = maxVal } } return coordinates } for _, ref := range strings.Split(cellRef, " ") { if !strings.Contains(ref, ":") { ref += ":" + ref } coordinates, err := rangeRefToCoordinates(ref) if err != nil { return "", err } if dir == columns { if offset < 0 && coordinates[0] == coordinates[2] && num == coordinates[0] { continue } coordinates = applyOffset(coordinates, 0, 2, MaxColumns) } else { if offset < 0 && coordinates[1] == coordinates[3] && num == coordinates[1] { continue } coordinates = applyOffset(coordinates, 1, 3, TotalRows) } if ref, err = coordinatesToRangeRef(coordinates); err != nil { return "", err } SQRef = append(SQRef, ref) } return strings.Join(SQRef, " "), nil } // adjustFormula provides a function to adjust formula reference and shared // formula reference. func (f *File) adjustFormula(sheet, sheetN string, cell *xlsxC, dir adjustDirection, num, offset int, si bool) error { var err error if cell.f != "" { if cell.f, err = f.adjustFormulaRef(sheet, sheetN, cell.f, false, dir, num, offset); err != nil { return err } } if cell.F == nil { return nil } if cell.F.Ref != "" && sheet == sheetN { if cell.F.Ref, err = f.adjustCellRef(cell.F.Ref, dir, num, offset); err != nil { return err } if si && cell.F.Si != nil { cell.F.Si = intPtr(*cell.F.Si + 1) } } if cell.F.Content != "" { if cell.F.Content, err = f.adjustFormulaRef(sheet, sheetN, cell.F.Content, false, dir, num, offset); err != nil { return err } } return nil } // escapeSheetName enclose sheet name in single quotation marks if the giving // worksheet name includes spaces or non-alphabetical characters. func escapeSheetName(name string) string { if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }) != -1 { return "'" + strings.ReplaceAll(name, "'", "''") + "'" } return name } // adjustFormulaColumnName adjust column name in the formula reference. func adjustFormulaColumnName(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) { if name == "" || (!abs && keepRelative) { return "", operand + name, abs, nil } col, err := ColumnNameToNumber(name) if err != nil { return "", operand, false, err } if dir == columns && col >= num { if col += offset; col < 1 { col = 1 } colName, err := ColumnNumberToName(col) return "", operand + colName, false, err } return "", operand + name, false, nil } // adjustFormulaRowNumber adjust row number in the formula reference. func adjustFormulaRowNumber(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) { if name == "" || (!abs && keepRelative) { return "", operand + name, abs, nil } row, _ := strconv.Atoi(name) if dir == rows && row >= num { if row += offset; row < 1 { row = 1 } if row > TotalRows { return "", operand + name, false, ErrMaxRows } return "", operand + strconv.Itoa(row), false, nil } return "", operand + name, false, nil } // adjustFormulaOperandRef adjust cell reference in the operand tokens for the formula. func adjustFormulaOperandRef(row, col, operand string, abs, keepRelative bool, dir adjustDirection, num int, offset int) (string, string, string, bool, error) { var err error col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset) if err != nil { return row, col, operand, abs, err } row, operand, abs, err = adjustFormulaRowNumber(row, operand, abs, keepRelative, dir, num, offset) return row, col, operand, abs, err } // adjustFormulaOperand adjust range operand tokens for the formula. func (f *File) adjustFormulaOperand(sheet, sheetN string, keepRelative bool, token efp.Token, dir adjustDirection, num int, offset int) (string, error) { var ( err error abs bool sheetName, col, row, operand string cell = token.TValue tokens = strings.Split(token.TValue, "!") ) if len(tokens) == 2 { // have a worksheet sheetName, cell = tokens[0], tokens[1] operand = escapeSheetName(sheetName) + "!" } if sheetName == "" { sheetName = sheetN } if sheet != sheetName { return operand + cell, err } for _, r := range cell { if r == '$' { if col, operand, _, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset); err != nil { return operand, err } abs = true operand += string(r) continue } if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') { col += string(r) continue } if '0' <= r && r <= '9' { row += string(r) col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset) if err != nil { return operand, err } continue } if row, col, operand, abs, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset); err != nil { return operand, err } operand += string(r) } _, _, operand, _, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset) return operand, err } // adjustFormulaRef returns adjusted formula by giving adjusting direction and // the base number of column or row, and offset. func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool, dir adjustDirection, num, offset int) (string, error) { var ( val string definedNames []string ps = efp.ExcelParser() ) for _, definedName := range f.GetDefinedName() { if definedName.Scope == "Workbook" || definedName.Scope == sheet { definedNames = append(definedNames, definedName.Name) } } for _, token := range ps.Parse(formula) { if token.TType == efp.TokenTypeUnknown { val = formula break } if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { if inStrSlice(definedNames, token.TValue, true) != -1 { val += token.TValue continue } if strings.ContainsAny(token.TValue, "[]") { val += token.TValue continue } operand, err := f.adjustFormulaOperand(sheet, sheetN, keepRelative, token, dir, num, offset) if err != nil { return val, err } val += operand continue } if paren := transformParenthesesToken(token); paren != "" { val += transformParenthesesToken(token) continue } if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText { val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble) continue } val += token.TValue } return val, nil } // transformParenthesesToken returns formula part with parentheses by given // token. func transformParenthesesToken(token efp.Token) string { if isFunctionStartToken(token) || isBeginParenthesesToken(token) { return token.TValue + string(efp.ParenOpen) } if isFunctionStopToken(token) || isEndParenthesesToken(token) { return token.TValue + string(efp.ParenClose) } return "" } // adjustRangeSheetName returns replaced range reference by given source and // target sheet name. func adjustRangeSheetName(rng, source, target string) string { source = escapeSheetName(source) cellRefs := strings.Split(rng, ",") for i, cellRef := range cellRefs { rangeRefs := strings.Split(cellRef, ":") for j, rangeRef := range rangeRefs { parts := strings.Split(rangeRef, "!") for k, part := range parts { if strings.TrimPrefix(strings.TrimSuffix(part, "'"), "'") == source { part = escapeSheetName(target) } parts[k] = part } rangeRefs[j] = strings.Join(parts, "!") } cellRefs[i] = strings.Join(rangeRefs, ":") } return strings.Join(cellRefs, ",") } // arrayFormulaOperandToken defines meta fields for transforming the array // formula to the normal formula. type arrayFormulaOperandToken struct { operandTokenIndex, topLeftCol, topLeftRow, bottomRightCol, bottomRightRow int sheetName, sourceCellRef, targetCellRef string } // setCoordinates convert each corner cell reference in the array formula cell // range to the coordinate number. func (af *arrayFormulaOperandToken) setCoordinates() error { for i, ref := range strings.Split(af.sourceCellRef, ":") { cellRef, col, row, err := parseRef(ref) if err != nil { return err } var c, r int if col { if cellRef.Row = TotalRows; i == 0 { cellRef.Row = 1 } } if row { if cellRef.Col = MaxColumns; i == 0 { cellRef.Col = 1 } } if c, r = cellRef.Col, cellRef.Row; cellRef.Sheet != "" { af.sheetName = cellRef.Sheet + "!" } if af.topLeftCol == 0 || c < af.topLeftCol { af.topLeftCol = c } if af.topLeftRow == 0 || r < af.topLeftRow { af.topLeftRow = r } if c > af.bottomRightCol { af.bottomRightCol = c } if r > af.bottomRightRow { af.bottomRightRow = r } } return nil } // transformArrayFormula transforms an array formula to the normal formula by // giving a formula tokens list and formula operand tokens list. func transformArrayFormula(tokens []efp.Token, afs []arrayFormulaOperandToken) string { var val string for i, token := range tokens { var skip bool for _, af := range afs { if af.operandTokenIndex == i { val += af.sheetName + af.targetCellRef skip = true break } } if skip { continue } if paren := transformParenthesesToken(token); paren != "" { val += transformParenthesesToken(token) continue } if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText { val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble) continue } val += token.TValue } return val } // getArrayFormulaTokens returns parsed formula token and operand related token // list for in array formula. func getArrayFormulaTokens(sheet, formula string, definedNames []DefinedName) ([]efp.Token, []arrayFormulaOperandToken, error) { var ( ps = efp.ExcelParser() tokens = ps.Parse(formula) arrayFormulaOperandTokens []arrayFormulaOperandToken ) for i, token := range tokens { if token.TSubType == efp.TokenSubTypeRange && token.TType == efp.TokenTypeOperand { tokenVal := token.TValue for _, definedName := range definedNames { if (definedName.Scope == "Workbook" || definedName.Scope == sheet) && definedName.Name == tokenVal { tokenVal = definedName.RefersTo } } if len(strings.Split(tokenVal, ":")) > 1 { arrayFormulaOperandToken := arrayFormulaOperandToken{ operandTokenIndex: i, sourceCellRef: tokenVal, } if err := arrayFormulaOperandToken.setCoordinates(); err != nil { return tokens, arrayFormulaOperandTokens, err } arrayFormulaOperandTokens = append(arrayFormulaOperandTokens, arrayFormulaOperandToken) } } } return tokens, arrayFormulaOperandTokens, nil } // adjustHyperlinks provides a function to update hyperlinks when inserting or // deleting rows or columns. func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) { // short path if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 { return } // order is important if offset < 0 { for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- { linkData := ws.Hyperlinks.Hyperlink[i] colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref) if (dir == rows && num == rowNum) || (dir == columns && num == colNum) { f.deleteSheetRelationships(sheet, linkData.RID) if len(ws.Hyperlinks.Hyperlink) > 1 { ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i], ws.Hyperlinks.Hyperlink[i+1:]...) } else { ws.Hyperlinks = nil } } } } if ws.Hyperlinks == nil { return } for i := range ws.Hyperlinks.Hyperlink { link := &ws.Hyperlinks.Hyperlink[i] // get reference link.Ref, _ = f.adjustFormulaRef(sheet, sheet, link.Ref, false, dir, num, offset) } } // adjustTable provides a function to update the table when inserting or // deleting rows or columns. func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 { return nil } for idx := 0; idx < len(ws.TableParts.TableParts); idx++ { tbl := ws.TableParts.TableParts[idx] target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID) tableXML := strings.ReplaceAll(target, "..", "xl") content, ok := f.Pkg.Load(tableXML) if !ok { continue } t := xlsxTable{} if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(&t); err != nil && err != io.EOF { return err } coordinates, err := rangeRefToCoordinates(t.Ref) if err != nil { return err } // Remove the table when deleting the header row of the table if dir == rows && num == coordinates[0] && offset == -1 { ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...) ws.TableParts.Count = len(ws.TableParts.TableParts) idx-- continue } coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset) x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if y2-y1 < 1 || x2-x1 < 0 { ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...) ws.TableParts.Count = len(ws.TableParts.TableParts) idx-- continue } t.Ref, _ = coordinatesToRangeRef([]int{x1, y1, x2, y2}) if t.AutoFilter != nil { t.AutoFilter.Ref = t.Ref } _ = f.setTableColumns(sheet, true, x1, y1, x2, &t) // Currently doesn't support query table t.TableType, t.TotalsRowCount, t.ConnectionID = "", 0, 0 table, _ := xml.Marshal(t) f.saveFileList(tableXML, table) } return nil } // adjustAutoFilter provides a function to update the auto filter when // inserting or deleting rows or columns. func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { if ws.AutoFilter == nil { return nil } coordinates, err := rangeRefToCoordinates(ws.AutoFilter.Ref) if err != nil { return err } x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) { ws.AutoFilter = nil for rowIdx := range ws.SheetData.Row { rowData := &ws.SheetData.Row[rowIdx] if rowData.R > y1 && rowData.R <= y2 { rowData.Hidden = false } } return err } coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset) x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3] ws.AutoFilter.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2}) return err } // adjustAutoFilterHelper provides a function for adjusting auto filter to // compare and calculate cell reference by the giving adjusting direction, // operation reference and offset. func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int { if dir == rows { if coordinates[1] >= num { coordinates[1] += offset } if coordinates[3] >= num { coordinates[3] += offset } return coordinates } if coordinates[0] >= num { coordinates[0] += offset } if coordinates[2] >= num { coordinates[2] += offset } return coordinates } // adjustMergeCells provides a function to update merged cells when inserting // or deleting rows or columns. func (f *File) adjustMergeCells(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { if ws.MergeCells == nil { return nil } for i := 0; i < len(ws.MergeCells.Cells); i++ { mergedCells := ws.MergeCells.Cells[i] mergedCellsRef := mergedCells.Ref if !strings.Contains(mergedCellsRef, ":") { mergedCellsRef += ":" + mergedCellsRef } coordinates, err := rangeRefToCoordinates(mergedCellsRef) if err != nil { return err } x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if dir == rows { if y1 == num && y2 == num && offset < 0 { f.deleteMergeCell(ws, i) i-- continue } y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset) } else { if x1 == num && x2 == num && offset < 0 { f.deleteMergeCell(ws, i) i-- continue } x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset) } if x1 == x2 && y1 == y2 { f.deleteMergeCell(ws, i) i-- continue } mergedCells.rect = []int{x1, y1, x2, y2} if mergedCells.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil { return err } } return nil } // adjustMergeCellsHelper provides a function for adjusting merge cells to // compare and calculate cell reference by the given pivot, operation reference // and offset. func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) { if p2 < p1 { p1, p2 = p2, p1 } if offset >= 0 { if num <= p1 { p1 += offset p2 += offset } else if num <= p2 { p2 += offset } return p1, p2 } if num < p1 || (num == p1 && num == p2) { p1 += offset p2 += offset } else if num <= p2 { p2 += offset } return p1, p2 } // deleteMergeCell provides a function to delete merged cell by given index. func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) { if idx < 0 { return } if len(ws.MergeCells.Cells) > idx { ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...) ws.MergeCells.Count = len(ws.MergeCells.Cells) } } // adjustCellName returns updated cell name by giving column/row number and // offset on inserting or deleting rows or columns. func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string, error) { if dir == rows { if rn := r + offset; rn > 0 { return CoordinatesToCellName(c, rn) } } return CoordinatesToCellName(c+offset, r) } // adjustCalcChain provides a function to update the calculation chain when // inserting or deleting rows or columns. func (f *File) adjustCalcChain(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { if f.CalcChain == nil { return nil } // If sheet ID is omitted, it is assumed to be the same as the i value of // the previous cell. var prevSheetID int for i := 0; f.CalcChain != nil && i < len(f.CalcChain.C); i++ { c := f.CalcChain.C[i] if c.I == 0 { c.I = prevSheetID } prevSheetID = c.I if c.I != sheetID { continue } colNum, rowNum, err := CellNameToCoordinates(c.R) if err != nil { return err } if dir == rows && num <= rowNum { if num == rowNum && offset == -1 { _ = f.deleteCalcChain(c.I, c.R) i-- continue } f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset) } if dir == columns && num <= colNum { if num == colNum && offset == -1 { _ = f.deleteCalcChain(c.I, c.R) i-- continue } f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset) } } return nil } // adjustVolatileDepsTopic updates the volatile dependencies topic when // inserting or deleting rows or columns. func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection, indexes []int) (int, error) { num, offset, i1, i2, i3, i4 := indexes[0], indexes[1], indexes[2], indexes[3], indexes[4], indexes[5] colNum, rowNum, err := CellNameToCoordinates(cell) if err != nil { return i4, err } if dir == rows && num <= rowNum { if num == rowNum && offset == -1 { vt.deleteVolTopicRef(i1, i2, i3, i4) i4-- return i4, err } vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset) } if dir == columns && num <= colNum { if num == colNum && offset == -1 { vt.deleteVolTopicRef(i1, i2, i3, i4) i4-- return i4, err } if name, _ := adjustCellName(cell, dir, colNum, rowNum, offset); name != "" { vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset) } } return i4, err } // adjustVolatileDeps updates the volatile dependencies when inserting or // deleting rows or columns. func (f *File) adjustVolatileDeps(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { volTypes, err := f.volatileDepsReader() if err != nil || volTypes == nil { return err } for i1 := 0; i1 < len(volTypes.VolType); i1++ { for i2 := 0; i2 < len(volTypes.VolType[i1].Main); i2++ { for i3 := 0; i3 < len(volTypes.VolType[i1].Main[i2].Tp); i3++ { for i4 := 0; i4 < len(volTypes.VolType[i1].Main[i2].Tp[i3].Tr); i4++ { ref := volTypes.VolType[i1].Main[i2].Tp[i3].Tr[i4] if ref.S != sheetID { continue } if i4, err = volTypes.adjustVolatileDepsTopic(ref.R, dir, []int{num, offset, i1, i2, i3, i4}); err != nil { return err } } } } } return nil } // adjustConditionalFormats updates the cell reference of the worksheet // conditional formatting when inserting or deleting rows or columns. func (f *File) adjustConditionalFormats(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { for i := 0; i < len(ws.ConditionalFormatting); i++ { cf := ws.ConditionalFormatting[i] if cf == nil { continue } ref, err := f.adjustCellRef(cf.SQRef, dir, num, offset) if err != nil { return err } if ref == "" { ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...) i-- continue } ws.ConditionalFormatting[i].SQRef = ref } return nil } // adjustDataValidations updates the range of data validations for the worksheet // when inserting or deleting rows or columns. func (f *File) adjustDataValidations(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { for _, sheetN := range f.GetSheetList() { worksheet, err := f.workSheetReader(sheetN) if err != nil { if err.Error() == newNotWorksheetError(sheetN).Error() { continue } return err } if worksheet.DataValidations == nil { continue } for i := 0; i < len(worksheet.DataValidations.DataValidation); i++ { dv := worksheet.DataValidations.DataValidation[i] if dv == nil { continue } if sheet == sheetN { ref, err := f.adjustCellRef(dv.Sqref, dir, num, offset) if err != nil { return err } if ref == "" { worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation[:i], worksheet.DataValidations.DataValidation[i+1:]...) i-- continue } worksheet.DataValidations.DataValidation[i].Sqref = ref } if worksheet.DataValidations.DataValidation[i].Formula1.isFormula() { formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula1.Content) if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil { return err } worksheet.DataValidations.DataValidation[i].Formula1 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)} } if worksheet.DataValidations.DataValidation[i].Formula2.isFormula() { formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula2.Content) if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil { return err } worksheet.DataValidations.DataValidation[i].Formula2 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)} } } if worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation); worksheet.DataValidations.Count == 0 { worksheet.DataValidations = nil } } return nil } // adjustDrawings updates the starting anchor of the two cell anchor pictures // and charts object when inserting or deleting rows or columns. func (from *xlsxFrom) adjustDrawings(dir adjustDirection, num, offset int, editAs string) (bool, error) { var ok bool if dir == columns && from.Col+1 >= num && from.Col+offset >= 0 { if from.Col+offset >= MaxColumns { return false, ErrColumnNumber } from.Col += offset ok = editAs == "oneCell" } if dir == rows && from.Row+1 >= num && from.Row+offset >= 0 { if from.Row+offset >= TotalRows { return false, ErrMaxRows } from.Row += offset ok = editAs == "oneCell" } return ok, nil } // adjustDrawings updates the ending anchor of the two cell anchor pictures // and charts object when inserting or deleting rows or columns. func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, ok bool) error { if dir == columns && to.Col+1 >= num && to.Col+offset >= 0 && ok { if to.Col+offset >= MaxColumns { return ErrColumnNumber } to.Col += offset } if dir == rows && to.Row+1 >= num && to.Row+offset >= 0 && ok { if to.Row+offset >= TotalRows { return ErrMaxRows } to.Row += offset } return nil } // adjustDrawings updates the two cell anchor pictures and charts object when // inserting or deleting rows or columns. func (a *xdrCellAnchor) adjustDrawings(dir adjustDirection, num, offset int) error { editAs := a.EditAs if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" { return nil } ok, err := a.From.adjustDrawings(dir, num, offset, editAs) if err != nil { return err } if a.To != nil { return a.To.adjustDrawings(dir, num, offset, ok || editAs == "") } return err } // adjustDrawings updates the existing two cell anchor pictures and charts // object when inserting or deleting rows or columns. func (a *xlsxCellAnchorPos) adjustDrawings(dir adjustDirection, num, offset int, editAs string) error { if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" { return nil } ok, err := a.From.adjustDrawings(dir, num, offset, editAs) if err != nil { return err } if a.To != nil { return a.To.adjustDrawings(dir, num, offset, ok || editAs == "") } return err } // adjustDrawings updates the pictures and charts object when inserting or // deleting rows or columns. func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) error { if ws.Drawing == nil { return nil } target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/") var ( err error wsDr *xlsxWsDr ) if wsDr, _, err = f.drawingParser(drawingXML); err != nil { return err } anchorCb := func(a *xdrCellAnchor) error { if a.GraphicFrame == "" { return a.adjustDrawings(dir, num, offset) } deCellAnchor := decodeCellAnchor{} deCellAnchorPos := decodeCellAnchorPos{} _ = f.xmlNewDecoder(strings.NewReader("" + a.GraphicFrame + "")).Decode(&deCellAnchor) _ = f.xmlNewDecoder(strings.NewReader("" + a.GraphicFrame + "")).Decode(&deCellAnchorPos) xlsxCellAnchorPos := xlsxCellAnchorPos(deCellAnchorPos) for i := 0; i < len(xlsxCellAnchorPos.AlternateContent); i++ { xlsxCellAnchorPos.AlternateContent[i].XMLNSMC = SourceRelationshipCompatibility.Value } if deCellAnchor.From != nil { xlsxCellAnchorPos.From = &xlsxFrom{ Col: deCellAnchor.From.Col, ColOff: deCellAnchor.From.ColOff, Row: deCellAnchor.From.Row, RowOff: deCellAnchor.From.RowOff, } } if deCellAnchor.To != nil { xlsxCellAnchorPos.To = &xlsxTo{ Col: deCellAnchor.To.Col, ColOff: deCellAnchor.To.ColOff, Row: deCellAnchor.To.Row, RowOff: deCellAnchor.To.RowOff, } } if err = xlsxCellAnchorPos.adjustDrawings(dir, num, offset, a.EditAs); err != nil { return err } cellAnchor, _ := xml.Marshal(xlsxCellAnchorPos) a.GraphicFrame = strings.TrimSuffix(strings.TrimPrefix(string(cellAnchor), ""), "") return err } for _, anchor := range wsDr.TwoCellAnchor { if err = anchorCb(anchor); err != nil { return err } } for _, anchor := range wsDr.OneCellAnchor { if err = anchorCb(anchor); err != nil { return err } } return nil } // adjustDefinedNames updates the cell reference of the defined names when // inserting or deleting rows or columns. func (f *File) adjustDefinedNames(sheet string, dir adjustDirection, num, offset int) error { wb, err := f.workbookReader() if err != nil { return err } if wb.DefinedNames != nil { for i := 0; i < len(wb.DefinedNames.DefinedName); i++ { data := wb.DefinedNames.DefinedName[i].Data if data, err = f.adjustFormulaRef(sheet, "", data, true, dir, num, offset); err == nil { wb.DefinedNames.DefinedName[i].Data = data } } } return nil } ================================================ FILE: adjust_test.go ================================================ package excelize import ( "encoding/xml" "fmt" "path/filepath" "strings" "testing" _ "image/jpeg" "github.com/stretchr/testify/assert" ) func TestAdjustMergeCells(t *testing.T) { f := NewFile() // Test adjustAutoFilter with illegal cell reference assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A:B1", }, }, }, }, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A1:B", }, }, }, }, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B"))) assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A1:B1", }, }, }, }, "Sheet1", rows, 1, -1, 1)) assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A1:A2", }, }, }, }, "Sheet1", columns, 1, -1, 1)) assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2", }, }, }, }, "Sheet1", columns, 1, -1, 1)) // Test adjust merge cells var cases []struct { label string ws *xlsxWorksheet dir adjustDirection num int offset int expect string expectRect []int } // Test adjust merged cell when insert rows and columns cases = []struct { label string ws *xlsxWorksheet dir adjustDirection num int offset int expect string expectRect []int }{ { label: "insert row on ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: rows, num: 2, offset: 1, expect: "A3:B4", expectRect: []int{1, 3, 2, 4}, }, { label: "insert row on bottom of ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: rows, num: 3, offset: 1, expect: "A2:B4", expectRect: []int{1, 2, 2, 4}, }, { label: "insert column on the left", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: columns, num: 1, offset: 1, expect: "B2:C3", expectRect: []int{2, 2, 3, 3}, }, } for _, c := range cases { assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, 1, 1)) assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label) assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label) } // Test adjust merged cells when delete rows and columns cases = []struct { label string ws *xlsxWorksheet dir adjustDirection num int offset int expect string expectRect []int }{ { label: "delete row on top of ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: rows, num: 2, offset: -1, expect: "A2:B2", expectRect: []int{1, 2, 2, 2}, }, { label: "delete row on bottom of ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: rows, num: 3, offset: -1, expect: "A2:B2", expectRect: []int{1, 2, 2, 2}, }, { label: "delete column on the ref left", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: columns, num: 1, offset: -1, expect: "A2:A3", expectRect: []int{1, 2, 1, 3}, }, { label: "delete column on the ref right", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A2:B3", rect: []int{1, 2, 2, 3}, }, }, }, }, dir: columns, num: 2, offset: -1, expect: "A2:A3", expectRect: []int{1, 2, 1, 3}, }, } for _, c := range cases { assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1)) assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label) } // Test delete one row or column cases = []struct { label string ws *xlsxWorksheet dir adjustDirection num int offset int expect string expectRect []int }{ { label: "delete one row ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A1:B1", rect: []int{1, 1, 2, 1}, }, }, }, }, dir: rows, num: 1, offset: -1, }, { label: "delete one column ref", ws: &xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ { Ref: "A1:A2", rect: []int{1, 1, 1, 2}, }, }, }, }, dir: columns, num: 1, offset: -1, }, } for _, c := range cases { assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1)) assert.Len(t, c.ws.MergeCells.Cells, 0, c.label) } f = NewFile() p1, p2 := f.adjustMergeCellsHelper(2, 1, 0, 0) assert.Equal(t, 1, p1) assert.Equal(t, 2, p2) f.deleteMergeCell(nil, -1) } func TestAdjustAutoFilter(t *testing.T) { f := NewFile() assert.NoError(t, f.adjustAutoFilter(&xlsxWorksheet{ SheetData: xlsxSheetData{ Row: []xlsxRow{{Hidden: true, R: 2}}, }, AutoFilter: &xlsxAutoFilter{ Ref: "A1:A3", }, }, "Sheet1", rows, 1, -1, 1)) // Test adjustAutoFilter with illegal cell reference assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{ AutoFilter: &xlsxAutoFilter{ Ref: "A:B1", }, }, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{ AutoFilter: &xlsxAutoFilter{ Ref: "A1:B", }, }, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B"))) } func TestAdjustTable(t *testing.T) { f, sheetName := NewFile(), "Sheet1" for idx, reference := range []string{"B2:C3", "E3:F5", "H5:H8", "J5:K9"} { assert.NoError(t, f.AddTable(sheetName, &Table{ Range: reference, Name: fmt.Sprintf("table%d", idx), StyleName: "TableStyleMedium2", ShowFirstColumn: true, ShowLastColumn: true, ShowRowStripes: boolPtr(false), ShowColumnStripes: true, })) } assert.NoError(t, f.RemoveRow(sheetName, 2)) assert.NoError(t, f.RemoveRow(sheetName, 3)) assert.NoError(t, f.RemoveRow(sheetName, 3)) assert.NoError(t, f.RemoveCol(sheetName, "H")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx"))) f = NewFile() assert.NoError(t, f.AddTable(sheetName, &Table{Range: "A1:D5"})) // Test adjust table with non-table part f.Pkg.Delete("xl/tables/table1.xml") assert.NoError(t, f.RemoveRow(sheetName, 1)) // Test adjust table with unsupported charset f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.RemoveRow(sheetName, 1), "XML syntax error on line 1: invalid UTF-8") // Test adjust table with invalid table range reference f.Pkg.Store("xl/tables/table1.xml", []byte(``)) assert.Equal(t, ErrParameterInvalid, f.RemoveRow(sheetName, 1)) } func TestAdjustHelper(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, }) f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{ AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}, }) // Test adjustHelper with illegal cell reference assert.Equal(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.Equal(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B"))) // Test adjustHelper on not exists worksheet assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN does not exist") } func TestAdjustCalcChain(t *testing.T) { f := NewFile() f.CalcChain = &xlsxCalcChain{ C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}, {R: "A1", I: 1}}, } assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) f.CalcChain = &xlsxCalcChain{ C: []xlsxCalcChainC{{R: "B2", I: 1}, {R: "B3"}, {R: "A1"}}, } assert.NoError(t, f.RemoveRow("Sheet1", 3)) assert.NoError(t, f.RemoveCol("Sheet1", "B")) f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}}} f.CalcChain.C[1].R = "invalid coordinates" assert.Equal(t, f.InsertCols("Sheet1", "A", 1), newCellNameToCoordinatesError("invalid coordinates", newInvalidCellNameError("invalid coordinates"))) f.CalcChain = nil assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) } func TestAdjustCols(t *testing.T) { sheetName := "Sheet1" preset := func() (*File, error) { f := NewFile() if err := f.SetColWidth(sheetName, "J", "T", 5); err != nil { return f, err } if err := f.SetSheetRow(sheetName, "J1", &[]string{"J1", "K1", "L1", "M1", "N1", "O1", "P1", "Q1", "R1", "S1", "T1"}); err != nil { return f, err } return f, nil } baseTbl := []string{"B", "J", "O", "O", "O", "U", "V"} insertTbl := []int{2, 2, 2, 5, 6, 2, 2} expectedTbl := []map[string]float64{ {"J": defaultColWidth, "K": defaultColWidth, "U": 5, "V": 5, "W": defaultColWidth}, {"J": defaultColWidth, "K": defaultColWidth, "U": 5, "V": 5, "W": defaultColWidth}, {"O": 5, "P": 5, "U": 5, "V": 5, "W": defaultColWidth}, {"O": 5, "S": 5, "X": 5, "Y": 5, "Z": defaultColWidth}, {"O": 5, "S": 5, "Y": 5, "X": 5, "AA": defaultColWidth}, {"U": 5, "V": 5, "W": defaultColWidth}, {"U": defaultColWidth, "V": defaultColWidth, "W": defaultColWidth}, } for idx, columnName := range baseTbl { f, err := preset() assert.NoError(t, err) assert.NoError(t, f.InsertCols(sheetName, columnName, insertTbl[idx])) for column, expected := range expectedTbl[idx] { width, err := f.GetColWidth(sheetName, column) assert.NoError(t, err) assert.Equal(t, expected, width, column) } assert.NoError(t, f.Close()) } baseTbl = []string{"B", "J", "O", "T"} expectedTbl = []map[string]float64{ {"H": defaultColWidth, "I": 5, "S": 5, "T": defaultColWidth}, {"I": defaultColWidth, "J": 5, "S": 5, "T": defaultColWidth}, {"I": defaultColWidth, "O": 5, "S": 5, "T": defaultColWidth}, {"R": 5, "S": 5, "T": defaultColWidth, "U": defaultColWidth}, } for idx, columnName := range baseTbl { f, err := preset() assert.NoError(t, err) assert.NoError(t, f.RemoveCol(sheetName, columnName)) for column, expected := range expectedTbl[idx] { width, err := f.GetColWidth(sheetName, column) assert.NoError(t, err) assert.Equal(t, expected, width, column) } assert.NoError(t, f.Close()) } f, err := preset() assert.NoError(t, err) assert.NoError(t, f.SetColWidth(sheetName, "I", "I", 8)) for i := 0; i <= 12; i++ { assert.NoError(t, f.RemoveCol(sheetName, "I")) } for c := 9; c <= 21; c++ { columnName, err := ColumnNumberToName(c) assert.NoError(t, err) width, err := f.GetColWidth(sheetName, columnName) assert.NoError(t, err) assert.Equal(t, defaultColWidth, width, columnName) } ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).Cols = nil assert.NoError(t, f.RemoveCol(sheetName, "A")) assert.NoError(t, f.Close()) f = NewFile() assert.NoError(t, f.SetColWidth("Sheet1", "XFB", "XFC", 12)) assert.NoError(t, f.InsertCols("Sheet1", "A", 2)) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Min) assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Max) assert.NoError(t, f.InsertCols("Sheet1", "A", 2)) assert.Nil(t, ws.(*xlsxWorksheet).Cols) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "(1-0.5)/2")) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) formula, err := f.GetCellFormula("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, "(1-0.5)/2", formula) assert.NoError(t, f.Close()) } func TestAdjustColDimensions(t *testing.T) { f := NewFile() ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1")) assert.Equal(t, ErrColumnNumber, f.adjustColDimensions("Sheet1", ws, 1, MaxColumns)) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet2.xml") f.Pkg.Store("xl/worksheets/sheet2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.adjustColDimensions("Sheet2", ws, 2, 1), "XML syntax error on line 1: invalid UTF-8") } func TestAdjustRowDimensions(t *testing.T) { f := NewFile() ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1")) assert.Equal(t, ErrMaxRows, f.adjustRowDimensions("Sheet1", ws, 1, TotalRows)) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet2.xml") f.Pkg.Store("xl/worksheets/sheet2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.adjustRowDimensions("Sheet1", ws, 2, 1), "XML syntax error on line 1: invalid UTF-8") f = NewFile() _, err = f.NewSheet("Sheet2") assert.NoError(t, err) ws, err = f.workSheetReader("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("Sheet2!A%d", TotalRows))) assert.Equal(t, ErrMaxRows, f.adjustRowDimensions("Sheet2", ws, 1, TotalRows)) } func TestAdjustHyperlinks(t *testing.T) { f := NewFile() ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1")) f.adjustHyperlinks(ws, "Sheet1", rows, 3, -1) // Test adjust hyperlinks location with positive offset assert.NoError(t, f.SetCellHyperLink("Sheet1", "F5", "Sheet1!A1", "Location")) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) link, target, err := f.GetCellHyperLink("Sheet1", "F6") assert.NoError(t, err) assert.True(t, link) assert.Equal(t, target, "Sheet1!A1") // Test adjust hyperlinks location with negative offset assert.NoError(t, f.RemoveRow("Sheet1", 1)) link, target, err = f.GetCellHyperLink("Sheet1", "F5") assert.NoError(t, err) assert.True(t, link) assert.Equal(t, target, "Sheet1!A1") // Test adjust hyperlinks location on remove row assert.NoError(t, f.RemoveRow("Sheet1", 5)) link, target, err = f.GetCellHyperLink("Sheet1", "F5") assert.NoError(t, err) assert.False(t, link) assert.Empty(t, target) // Test adjust hyperlinks location on remove column assert.NoError(t, f.SetCellHyperLink("Sheet1", "F5", "Sheet1!A1", "Location")) assert.NoError(t, f.RemoveCol("Sheet1", "F")) link, target, err = f.GetCellHyperLink("Sheet1", "F5") assert.NoError(t, err) assert.False(t, link) assert.Empty(t, target) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustHyperlinks.xlsx"))) assert.NoError(t, f.Close()) } func TestAdjustFormula(t *testing.T) { f := NewFile() formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) assert.NoError(t, f.InsertCols("Sheet1", "B", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) for cell, expected := range map[string]string{"D2": "A2+C2", "D3": "A3+C3", "D11": "A11+C11"} { formula, err := f.GetCellFormula("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, formula) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx"))) assert.NoError(t, f.Close()) assert.NoError(t, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{}, rows, 0, 0, false)) assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "-"}}, rows, 0, 0, false)) assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "XFD1:XFD1"}}, columns, 0, 1, false)) _, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", false, columns, 0, 1) assert.Equal(t, ErrColumnNumber, err) _, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", false, columns, 0, 1) assert.Equal(t, ErrColumnNumber, err) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "XFD1")) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("A%d", TotalRows))) assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(1048576:1:2)")) assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(XFD:A:B)")) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(A:B:XFD)")) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) // Test adjust formula with defined name in formula text f = NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$B$2", })) assert.NoError(t, f.SetCellFormula("Sheet1", "B2", "Amount+B3")) assert.NoError(t, f.RemoveRow("Sheet1", 1)) formula, err := f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "Amount+B2", formula) // Test adjust formula with array formula f = NewFile() formulaType, reference := STCellFormulaTypeArray, "A3:A3" assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) formula, err = f.GetCellFormula("Sheet1", "A4") assert.NoError(t, err) assert.Equal(t, "A2:A3", formula) // Test adjust formula on duplicate row with array formula f = NewFile() formulaType, reference = STCellFormulaTypeArray, "A3" assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) formula, err = f.GetCellFormula("Sheet1", "A4") assert.NoError(t, err) assert.Equal(t, "A2:A3", formula) // Test adjust formula on duplicate row with relative and absolute cell references f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "A$10+$A11&\" \"")) assert.NoError(t, f.DuplicateRowTo("Sheet1", 10, 2)) formula, err = f.GetCellFormula("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, "A$2+$A3&\" \"", formula) t.Run("for_cells_affected_directly", func(t *testing.T) { // Test insert row in middle of range with relative and absolute cell references f := NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "$A1+A$2")) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err := f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "$A1+A$3", formula) assert.NoError(t, f.RemoveRow("Sheet1", 2)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "$A1+A$2", formula) // Test insert column in middle of range f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "B1+C1")) assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "B1+D1", formula) assert.NoError(t, f.RemoveCol("Sheet1", "C")) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "B1+C1", formula) // Test insert row and column in a rectangular range f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "D4+D5+E4+E5")) assert.NoError(t, f.InsertCols("Sheet1", "E", 1)) assert.NoError(t, f.InsertRows("Sheet1", 5, 1)) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "D4+D6+F4+F6", formula) // Test insert row in middle of range f = NewFile() formulaType, reference := STCellFormulaTypeArray, "B1:B1" assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "A1:A3", formula) assert.NoError(t, f.RemoveRow("Sheet1", 2)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "A1:A2", formula) // Test insert column in middle of range f = NewFile() formulaType, reference = STCellFormulaTypeArray, "A1:A1" assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "B1:C1", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "B1:D1", formula) assert.NoError(t, f.RemoveCol("Sheet1", "C")) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "B1:C1", formula) // Test insert row and column in a rectangular range f = NewFile() formulaType, reference = STCellFormulaTypeArray, "A1:A1" assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "D4:E5", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertCols("Sheet1", "E", 1)) assert.NoError(t, f.InsertRows("Sheet1", 5, 1)) formula, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "D4:F6", formula) }) t.Run("for_cells_affected_indirectly", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A3+A4")) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err := f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "A4+A5", formula) assert.NoError(t, f.RemoveRow("Sheet1", 2)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "A3+A4", formula) f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "D3+D4")) assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "E3+E4", formula) assert.NoError(t, f.RemoveCol("Sheet1", "C")) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "D3+D4", formula) }) t.Run("for_entire_cols_rows_reference", func(t *testing.T) { f := NewFile() // Test adjust formula on insert row in the middle of the range assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(A2:A3:A4,,Table1[])")) assert.NoError(t, f.InsertRows("Sheet1", 3, 1)) formula, err := f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(A2:A4:A5,,Table1[])", formula) // Test adjust formula on insert at the top of the range assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(A3:A5:A6,,Table1[])", formula) f = NewFile() // Test adjust formula on insert row in the middle of the range assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM('Sheet 1'!A2,A3)")) assert.NoError(t, f.InsertRows("Sheet1", 3, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM('Sheet 1'!A2,A4)", formula) // Test adjust formula on insert row at the top of the range assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM('Sheet 1'!A2,A5)", formula) f = NewFile() // Test adjust formula on insert col in the middle of the range assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C3:D3)")) assert.NoError(t, f.InsertCols("Sheet1", "D", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(C3:E3)", formula) // Test adjust formula on insert at the top of the range assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(D3:F3)", formula) f = NewFile() // Test adjust formula on insert column in the middle of the range assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C3,D3)")) assert.NoError(t, f.InsertCols("Sheet1", "D", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(C3,E3)", formula) // Test adjust formula on insert column at the top of the range assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(D3,F3)", formula) f = NewFile() // Test adjust formula on insert row in the middle of the range (range of whole row) assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(2:3)")) assert.NoError(t, f.InsertRows("Sheet1", 3, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(2:4)", formula) // Test adjust formula on insert row at the top of the range (range of whole row) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(3:5)", formula) f = NewFile() // Test adjust formula on insert row in the middle of the range (range of whole column) assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C:D)")) assert.NoError(t, f.InsertCols("Sheet1", "D", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(C:E)", formula) // Test adjust formula on insert row at the top of the range (range of whole column) assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) formula, err = f.GetCellFormula("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "SUM(D:F)", formula) }) t.Run("for_all_worksheet_cells_with_rows_insert", func(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) // Tests formulas referencing Sheet2 should update but those referencing the original sheet should not tbl := [][]string{ {"B1", "Sheet2!A1+Sheet2!A2", "Sheet2!A1+Sheet2!A3", "Sheet2!A2+Sheet2!A4"}, {"C1", "A1+A2", "A1+A2", "A1+A2"}, {"D1", "Sheet2!B1:B2", "Sheet2!B1:B3", "Sheet2!B2:B4"}, {"E1", "B1:B2", "B1:B2", "B1:B2"}, {"F1", "SUM(Sheet2!C1:C2)", "SUM(Sheet2!C1:C3)", "SUM(Sheet2!C2:C4)"}, {"G1", "SUM(C1:C2)", "SUM(C1:C2)", "SUM(C1:C2)"}, {"H1", "SUM(Sheet2!D1,Sheet2!D2)", "SUM(Sheet2!D1,Sheet2!D3)", "SUM(Sheet2!D2,Sheet2!D4)"}, {"I1", "SUM(D1,D2)", "SUM(D1,D2)", "SUM(D1,D2)"}, } for _, preset := range tbl { assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1])) } // Test adjust formula on insert row in the middle of the range assert.NoError(t, f.InsertRows("Sheet2", 2, 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[2], formula) } // Test adjust formula on insert row in the top of the range assert.NoError(t, f.InsertRows("Sheet2", 1, 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[3], formula) } }) t.Run("for_all_worksheet_cells_with_cols_insert", func(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) tbl := [][]string{ {"A1", "Sheet2!A1+Sheet2!B1", "Sheet2!A1+Sheet2!C1", "Sheet2!B1+Sheet2!D1"}, {"A2", "A1+B1", "A1+B1", "A1+B1"}, {"A3", "Sheet2!A2:B2", "Sheet2!A2:C2", "Sheet2!B2:D2"}, {"A4", "A2:B2", "A2:B2", "A2:B2"}, {"A5", "SUM(Sheet2!A3:B3)", "SUM(Sheet2!A3:C3)", "SUM(Sheet2!B3:D3)"}, {"A6", "SUM(A3:B3)", "SUM(A3:B3)", "SUM(A3:B3)"}, {"A7", "SUM(Sheet2!A4,Sheet2!B4)", "SUM(Sheet2!A4,Sheet2!C4)", "SUM(Sheet2!B4,Sheet2!D4)"}, {"A8", "SUM(A4,B4)", "SUM(A4,B4)", "SUM(A4,B4)"}, } for _, preset := range tbl { assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1])) } // Test adjust formula on insert column in the middle of the range assert.NoError(t, f.InsertCols("Sheet2", "B", 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[2], formula) } // Test adjust formula on insert column in the top of the range assert.NoError(t, f.InsertCols("Sheet2", "A", 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[3], formula) } }) t.Run("for_cross_sheet_ref_with_rows_insert)", func(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) // Tests formulas referencing Sheet2 should update but those referencing // the original sheet or Sheet 3 should not update tbl := [][]string{ {"B1", "Sheet2!A1+Sheet2!A2+Sheet1!A3+Sheet1!A4", "Sheet2!A1+Sheet2!A3+Sheet1!A3+Sheet1!A4", "Sheet2!A2+Sheet2!A4+Sheet1!A3+Sheet1!A4"}, {"C1", "Sheet2!B1+Sheet2!B2+B3+B4", "Sheet2!B1+Sheet2!B3+B3+B4", "Sheet2!B2+Sheet2!B4+B3+B4"}, {"D1", "Sheet2!C1+Sheet2!C2+Sheet3!A3+Sheet3!A4", "Sheet2!C1+Sheet2!C3+Sheet3!A3+Sheet3!A4", "Sheet2!C2+Sheet2!C4+Sheet3!A3+Sheet3!A4"}, {"E1", "SUM(Sheet2!D1:D2,Sheet1!A3:A4)", "SUM(Sheet2!D1:D3,Sheet1!A3:A4)", "SUM(Sheet2!D2:D4,Sheet1!A3:A4)"}, {"F1", "SUM(Sheet2!E1:E2,A3:A4)", "SUM(Sheet2!E1:E3,A3:A4)", "SUM(Sheet2!E2:E4,A3:A4)"}, {"G1", "SUM(Sheet2!F1:F2,Sheet3!A3:A4)", "SUM(Sheet2!F1:F3,Sheet3!A3:A4)", "SUM(Sheet2!F2:F4,Sheet3!A3:A4)"}, } for _, preset := range tbl { assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1])) } // Test adjust formula on insert row in the middle of the range assert.NoError(t, f.InsertRows("Sheet2", 2, 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[2], formula) } // Test adjust formula on insert row in the top of the range assert.NoError(t, f.InsertRows("Sheet2", 1, 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[3], formula) } }) t.Run("for_cross_sheet_ref_with_cols_insert)", func(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) // Tests formulas referencing Sheet2 should update but those referencing // the original sheet or Sheet 3 should not update tbl := [][]string{ {"A1", "Sheet2!A1+Sheet2!B1+Sheet1!C1+Sheet1!D1", "Sheet2!A1+Sheet2!C1+Sheet1!C1+Sheet1!D1", "Sheet2!B1+Sheet2!D1+Sheet1!C1+Sheet1!D1"}, {"A2", "Sheet2!A2+Sheet2!B2+C2+D2", "Sheet2!A2+Sheet2!C2+C2+D2", "Sheet2!B2+Sheet2!D2+C2+D2"}, {"A3", "Sheet2!A3+Sheet2!B3+Sheet3!C3+Sheet3!D3", "Sheet2!A3+Sheet2!C3+Sheet3!C3+Sheet3!D3", "Sheet2!B3+Sheet2!D3+Sheet3!C3+Sheet3!D3"}, {"A4", "SUM(Sheet2!A4:B4,Sheet1!C4:D4)", "SUM(Sheet2!A4:C4,Sheet1!C4:D4)", "SUM(Sheet2!B4:D4,Sheet1!C4:D4)"}, {"A5", "SUM(Sheet2!A5:B5,C5:D5)", "SUM(Sheet2!A5:C5,C5:D5)", "SUM(Sheet2!B5:D5,C5:D5)"}, {"A6", "SUM(Sheet2!A6:B6,Sheet3!C6:D6)", "SUM(Sheet2!A6:C6,Sheet3!C6:D6)", "SUM(Sheet2!B6:D6,Sheet3!C6:D6)"}, } for _, preset := range tbl { assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1])) } // Test adjust formula on insert row in the middle of the range assert.NoError(t, f.InsertCols("Sheet2", "B", 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[2], formula) } // Test adjust formula on insert row in the top of the range assert.NoError(t, f.InsertCols("Sheet2", "A", 1)) for _, preset := range tbl { formula, err := f.GetCellFormula("Sheet1", preset[0]) assert.NoError(t, err) assert.Equal(t, preset[3], formula) } }) t.Run("for_cross_sheet_ref_with_chart_sheet)", func(t *testing.T) { assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line})) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) }) t.Run("for_array_formula_cell", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2})) assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4})) formulaType, ref := STCellFormulaTypeArray, "C1:C2" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) result, err := f.CalcCellValue("Sheet1", "D2") assert.NoError(t, err) assert.Equal(t, "2", result) result, err = f.CalcCellValue("Sheet1", "D3") assert.NoError(t, err) assert.Equal(t, "12", result) // Test adjust array formula with invalid range reference formulaType, ref = STCellFormulaTypeArray, "E1:E2" assert.NoError(t, f.SetCellFormula("Sheet1", "E1", "XFD1:XFD1", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) }) } func TestAdjustVolatileDeps(t *testing.T) { f := NewFile() f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`
`, NameSpaceSpreadSheet.Value))) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) assert.Equal(t, "D3", f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr[1].R) assert.NoError(t, f.RemoveCol("Sheet1", "D")) assert.NoError(t, f.RemoveRow("Sheet1", 4)) assert.Len(t, f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr, 1) f = NewFile() f.Pkg.Store(defaultXMLPathVolatileDeps, MacintoshCyrillicCharset) assert.EqualError(t, f.InsertRows("Sheet1", 2, 1), "XML syntax error on line 1: invalid UTF-8") f = NewFile() f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`
`, NameSpaceSpreadSheet.Value))) assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.InsertCols("Sheet1", "A", 1)) f.volatileDepsWriter() } func TestAdjustConditionalFormats(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "B1", &[]interface{}{1, nil, 1, 1})) formatID, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "09600B"}, Fill: Fill{Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1}}) assert.NoError(t, err) format := []ConditionalFormatOptions{ { Type: "cell", Criteria: "greater than", Format: &formatID, Value: "0", }, } for _, ref := range []string{"B1", "D1:E1"} { assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, format)) } assert.NoError(t, f.RemoveCol("Sheet1", "B")) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, format, 1) assert.Equal(t, format, opts["C1:D1"]) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ConditionalFormatting[0].SQRef = "-" assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.RemoveCol("Sheet1", "B")) ws.(*xlsxWorksheet).ConditionalFormatting[0] = nil assert.NoError(t, f.RemoveCol("Sheet1", "B")) t.Run("for_remove_conditional_formats_column", func(t *testing.T) { f := NewFile() format := []ConditionalFormatOptions{{ Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", }} assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format)) assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format)) assert.NoError(t, f.RemoveCol("Sheet1", "D")) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, opts, 0) }) t.Run("for_remove_conditional_formats_row", func(t *testing.T) { f := NewFile() format := []ConditionalFormatOptions{{ Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", }} assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:E2", format)) assert.NoError(t, f.SetConditionalFormat("Sheet1", "F2", format)) assert.NoError(t, f.RemoveRow("Sheet1", 2)) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, opts, 0) }) t.Run("for_adjust_conditional_formats_row", func(t *testing.T) { f := NewFile() format := []ConditionalFormatOptions{{ Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", }} assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format)) assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format)) assert.NoError(t, f.RemoveRow("Sheet1", 1)) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, opts, 2) assert.Equal(t, format, opts["D1:D2"]) assert.Equal(t, format, opts["D4:D4"]) }) } func TestAdjustDataValidations(t *testing.T) { f := NewFile() dv := NewDataValidation(true) dv.Sqref = "B1" assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"})) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.RemoveCol("Sheet1", "B")) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dvs, 0) assert.NoError(t, f.SetCellValue("Sheet1", "F2", 1)) assert.NoError(t, f.SetCellValue("Sheet1", "F3", 2)) dv = NewDataValidation(true) dv.Sqref = "C2:D3" dv.SetSqrefDropList("$F$2:$F$3") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line})) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet2", "C1", &[]interface{}{1, 10})) dv = NewDataValidation(true) dv.Sqref = "C5:D6" assert.NoError(t, dv.SetRange("Sheet2!C1", "Sheet2!D1", DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetError(DataValidationErrorStyleStop, "error title", "error body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.RemoveCol("Sheet1", "B")) assert.NoError(t, f.RemoveCol("Sheet2", "B")) dvs, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, "B2:C3", dvs[0].Sqref) assert.Equal(t, "$E$2:$E$3", dvs[0].Formula1) assert.Equal(t, "B5:C6", dvs[1].Sqref) assert.Equal(t, "Sheet2!B1", dvs[1].Formula1) assert.Equal(t, "Sheet2!C1", dvs[1].Formula2) dv = NewDataValidation(true) dv.Sqref = "C8:D10" assert.NoError(t, dv.SetDropList([]string{`A<`, `B>`, `C"`, "D\t", `E'`, `F`})) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.RemoveCol("Sheet1", "B")) dvs, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, "\"A<,B>,C\",D\t,E',F\"", dvs[2].Formula1) // Test adjust data validation with multiple cell range dv = NewDataValidation(true) dv.Sqref = "G1:G3 H1:H3 A3:A1048576" assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"})) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.InsertRows("Sheet1", 2, 1)) dvs, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, "G1:G4 H1:H4 A4:A1048576", dvs[3].Sqref) dv = NewDataValidation(true) dv.Sqref = "C5:D6" assert.NoError(t, dv.SetRange("Sheet1!A1048576", "Sheet1!XFD1", DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetError(DataValidationErrorStyleStop, "error title", "error body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "-" assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.RemoveCol("Sheet1", "B")) ws.(*xlsxWorksheet).DataValidations.DataValidation[0] = nil assert.NoError(t, f.RemoveCol("Sheet1", "B")) ws.(*xlsxWorksheet).DataValidations = nil assert.NoError(t, f.RemoveCol("Sheet1", "B")) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.adjustDataValidations(nil, "Sheet1", columns, 0, 0, 1), "XML syntax error on line 1: invalid UTF-8") t.Run("for_escaped_data_validation_rules_formula", func(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) dv := NewDataValidation(true) dv.Sqref = "A1" assert.NoError(t, dv.SetDropList([]string{"option1", strings.Repeat("\"", 4)})) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) // The double quote symbol in none formula data validation rules will be escaped in the Kingsoft WPS Office formula := strings.ReplaceAll(fmt.Sprintf("\"option1, %s", strings.Repeat("\"", 9)), "\"", """) ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Formula1.Content = formula assert.NoError(t, f.RemoveCol("Sheet2", "A")) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, formula, dvs[0].Formula1) }) t.Run("no_data_validations_on_first_sheet", func(t *testing.T) { f := NewFile() // Add Sheet2 and set a data validation _, err = f.NewSheet("Sheet2") assert.NoError(t, err) dv := NewDataValidation(true) dv.Sqref = "C5:D6" assert.NoError(t, f.AddDataValidation("Sheet2", dv)) // Adjust Sheet2 by removing a column assert.NoError(t, f.RemoveCol("Sheet2", "A")) // Verify that data validations on Sheet2 are adjusted correctly dvs, err = f.GetDataValidations("Sheet2") assert.NoError(t, err) assert.Equal(t, "B5:C6", dvs[0].Sqref) // Adjusted range }) } func TestAdjustDrawings(t *testing.T) { f := NewFile() // Test add pictures to sheet with positioning assert.NoError(t, f.AddPicture("Sheet1", "B2", filepath.Join("test", "images", "excel.jpg"), nil)) assert.NoError(t, f.AddPicture("Sheet1", "B11", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "oneCell"})) assert.NoError(t, f.AddPicture("Sheet1", "B21", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "absolute"})) // Test adjust pictures on inserting columns and rows assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.InsertCols("Sheet1", "C", 1)) assert.NoError(t, f.InsertRows("Sheet1", 5, 1)) assert.NoError(t, f.InsertRows("Sheet1", 15, 1)) cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"D3", "B21", "D13"}, cells) wb := filepath.Join("test", "TestAdjustDrawings.xlsx") assert.NoError(t, f.SaveAs(wb)) // Test adjust pictures on deleting columns and rows assert.NoError(t, f.RemoveCol("Sheet1", "A")) assert.NoError(t, f.RemoveRow("Sheet1", 1)) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"C2", "B21", "C12"}, cells) // Test adjust existing pictures on inserting columns and rows f, err = OpenFile(wb) assert.NoError(t, err) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.InsertCols("Sheet1", "D", 1)) assert.NoError(t, f.InsertRows("Sheet1", 5, 1)) assert.NoError(t, f.InsertRows("Sheet1", 16, 1)) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"F4", "B21", "F15"}, cells) // Test adjust drawings with unsupported charset f, err = OpenFile(wb) assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), "XML syntax error on line 1: invalid UTF-8") errors := []error{ErrColumnNumber, ErrColumnNumber, ErrMaxRows, ErrMaxRows} cells = []string{"XFD1", "XFB1"} for i, cell := range cells { f = NewFile() assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil)) assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.SaveAs(wb)) f, err = OpenFile(wb) assert.NoError(t, err) assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1)) } errors = []error{ErrMaxRows, ErrMaxRows} cells = []string{"A1048576", "A1048570"} for i, cell := range cells { f = NewFile() assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil)) assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.SaveAs(wb)) f, err = OpenFile(wb) assert.NoError(t, err) assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1)) } a := xdrCellAnchor{} assert.NoError(t, a.adjustDrawings(columns, 0, 0)) p := xlsxCellAnchorPos{} assert.NoError(t, p.adjustDrawings(columns, 0, 0, "")) f, err = OpenFile(wb) assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`00001010`)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) f, err = OpenFile(wb) assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+fmt.Sprintf(`%d0`, MaxColumns))) assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) } func TestAdjustDefinedNames(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) for _, dn := range []*DefinedName{ {Name: "Name1", RefersTo: "Sheet1!$XFD$1"}, {Name: "Name2", RefersTo: "Sheet2!$C$1", Scope: "Sheet1"}, {Name: "Name3", RefersTo: "Sheet2!$C$1:$D$2", Scope: "Sheet1"}, {Name: "Name4", RefersTo: "Sheet2!$C1:D$2"}, {Name: "Name5", RefersTo: "Sheet2!C$1:$D2"}, {Name: "Name6", RefersTo: "Sheet2!C:$D"}, {Name: "Name7", RefersTo: "Sheet2!$C:D"}, {Name: "Name8", RefersTo: "Sheet2!C:D"}, {Name: "Name9", RefersTo: "Sheet2!$C:$D"}, {Name: "Name10", RefersTo: "Sheet2!1:2"}, } { assert.NoError(t, f.SetDefinedName(dn)) } assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.InsertCols("Sheet2", "A", 1)) assert.NoError(t, f.InsertRows("Sheet2", 1, 1)) definedNames := f.GetDefinedName() for i, expected := range []string{ "Sheet1!$XFD$2", "Sheet2!$D$2", "Sheet2!$D$2:$E$3", "Sheet2!$D1:D$3", "Sheet2!C$2:$E2", "Sheet2!C:$E", "Sheet2!$D:D", "Sheet2!C:D", "Sheet2!$D:$E", "Sheet2!1:2", } { assert.Equal(t, expected, definedNames[i].RefersTo) } f = NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name1", RefersTo: "Sheet1!$A$1", Scope: "Sheet1", })) assert.NoError(t, f.RemoveCol("Sheet1", "A")) definedNames = f.GetDefinedName() assert.Equal(t, "Sheet1!$A$1", definedNames[0].RefersTo) f = NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name1", RefersTo: "'1.A & B C'!#REF!", Scope: "Sheet1", })) assert.NoError(t, f.RemoveCol("Sheet1", "A")) definedNames = f.GetDefinedName() assert.Equal(t, "'1.A & B C'!#REF!", definedNames[0].RefersTo) f = NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.adjustDefinedNames("Sheet1", columns, 0, 0), "XML syntax error on line 1: invalid UTF-8") } ================================================ FILE: calc.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "container/list" "errors" "fmt" "math" "math/big" "math/cmplx" "math/rand" "net/url" "reflect" "regexp" "sort" "strconv" "strings" "sync" "time" "unicode" "unicode/utf8" "unsafe" "github.com/xuri/efp" "golang.org/x/text/language" "golang.org/x/text/message" ) const ( // Excel formula errors formulaErrorDIV = "#DIV/0!" formulaErrorNAME = "#NAME?" formulaErrorNA = "#N/A" formulaErrorNUM = "#NUM!" formulaErrorVALUE = "#VALUE!" formulaErrorREF = "#REF!" formulaErrorNULL = "#NULL!" formulaErrorSPILL = "#SPILL!" formulaErrorCALC = "#CALC!" formulaErrorGETTINGDATA = "#GETTING_DATA" // Formula criteria condition enumeration _ byte = iota criteriaEq criteriaLe criteriaGe criteriaNe criteriaL criteriaG criteriaErr criteriaRegexp categoryWeightAndMass categoryDistance categoryTime categoryPressure categoryForce categoryEnergy categoryPower categoryMagnetism categoryTemperature categoryVolumeAndLiquidMeasure categoryArea categoryInformation categorySpeed matchModeExact = 0 matchModeMinGreater = 1 matchModeMaxLess = -1 matchModeWildcard = 2 searchModeLinear = 1 searchModeReverseLinear = -1 searchModeAscBinary = 2 searchModeDescBinary = -2 maxFinancialIterations = 128 financialPrecision = 1.0e-08 // Date and time format regular expressions monthRe = `((jan|january)|(feb|february)|(mar|march)|(apr|april)|(may)|(jun|june)|(jul|july)|(aug|august)|(sep|september)|(oct|october)|(nov|november)|(dec|december))` df1 = `(([0-9])+)/(([0-9])+)/(([0-9])+)` df2 = monthRe + ` (([0-9])+), (([0-9])+)` df3 = `(([0-9])+)-(([0-9])+)-(([0-9])+)` df4 = `(([0-9])+)-` + monthRe + `-(([0-9])+)` datePrefix = `^((` + df1 + `|` + df2 + `|` + df3 + `|` + df4 + `) )?` tfhh = `(([0-9])+) (am|pm)` tfhhmm = `(([0-9])+):(([0-9])+)( (am|pm))?` tfmmss = `(([0-9])+):(([0-9])+\.([0-9])+)( (am|pm))?` tfhhmmss = `(([0-9])+):(([0-9])+):(([0-9])+(\.([0-9])+)?)( (am|pm))?` timeSuffix = `( (` + tfhh + `|` + tfhhmm + `|` + tfmmss + `|` + tfhhmmss + `))?$` ) var ( // wildcardTokenRE tokenizes an Excel wildcard pattern into tilde-escaped // sequences, bare wildcards (* ?), or any other single character. wildcardTokenRE = regexp.MustCompile(`~[*?~]|[*?]|[\s\S]`) // wildcardPatternMap maps each token produced by wildcardTokenRE to its // regular-expression equivalent. Tokens absent from the map are literals. wildcardPatternMap = map[string]string{ "~*": regexp.QuoteMeta("*"), "~?": regexp.QuoteMeta("?"), "~~": regexp.QuoteMeta("~"), "*": ".*", "?": ".", } // wildcardBareTokens is the set of tokens that represent real wildcards. wildcardBareTokens = map[string]bool{"*": true, "?": true} // tokenPriority defined basic arithmetic operator priority tokenPriority = map[string]int{ "^": 5, "*": 4, "/": 4, "+": 3, "-": 3, "&": 2, "=": 1, "<>": 1, "<": 1, "<=": 1, ">": 1, ">=": 1, } month2num = map[string]int{ "january": 1, "february": 2, "march": 3, "april": 4, "may": 5, "june": 6, "july": 7, "august": 8, "september": 9, "october": 10, "november": 11, "december": 12, "jan": 1, "feb": 2, "mar": 3, "apr": 4, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, } dateFormats = map[string]*regexp.Regexp{ "mm/dd/yy": regexp.MustCompile(`^` + df1 + timeSuffix), "mm dd, yy": regexp.MustCompile(`^` + df2 + timeSuffix), "yy-mm-dd": regexp.MustCompile(`^` + df3 + timeSuffix), "yy-mmStr-dd": regexp.MustCompile(`^` + df4 + timeSuffix), } timeFormats = map[string]*regexp.Regexp{ "hh": regexp.MustCompile(datePrefix + tfhh + `$`), "hh:mm": regexp.MustCompile(datePrefix + tfhhmm + `$`), "mm:ss": regexp.MustCompile(datePrefix + tfmmss + `$`), "hh:mm:ss": regexp.MustCompile(datePrefix + tfhhmmss + `$`), } dateOnlyFormats = []*regexp.Regexp{ regexp.MustCompile(`^` + df1 + `$`), regexp.MustCompile(`^` + df2 + `$`), regexp.MustCompile(`^` + df3 + `$`), regexp.MustCompile(`^` + df4 + `$`), } addressFmtMaps = map[string]func(col, row int) (string, error){ "1_TRUE": func(col, row int) (string, error) { return CoordinatesToCellName(col, row, true) }, "1_FALSE": func(col, row int) (string, error) { return fmt.Sprintf("R%dC%d", row, col), nil }, "2_TRUE": func(col, row int) (string, error) { column, err := ColumnNumberToName(col) if err != nil { return "", err } return fmt.Sprintf("%s$%d", column, row), nil }, "2_FALSE": func(col, row int) (string, error) { return fmt.Sprintf("R%dC[%d]", row, col), nil }, "3_TRUE": func(col, row int) (string, error) { column, err := ColumnNumberToName(col) if err != nil { return "", err } return fmt.Sprintf("$%s%d", column, row), nil }, "3_FALSE": func(col, row int) (string, error) { return fmt.Sprintf("R[%d]C%d", row, col), nil }, "4_TRUE": func(col, row int) (string, error) { return CoordinatesToCellName(col, row, false) }, "4_FALSE": func(col, row int) (string, error) { return fmt.Sprintf("R[%d]C[%d]", row, col), nil }, } formulaFnNameReplacer = strings.NewReplacer("_xlfn.", "", ".", "dot") formulaFormats = []*regexp.Regexp{ regexp.MustCompile(`^(\d+)$`), regexp.MustCompile(`^=(.*)$`), regexp.MustCompile(`^<>(.*)$`), regexp.MustCompile(`^<=(.*)$`), regexp.MustCompile(`^>=(.*)$`), regexp.MustCompile(`^<(.*)$`), regexp.MustCompile(`^>(.*)$`), } formulaCriterias = []byte{ criteriaEq, criteriaEq, criteriaNe, criteriaLe, criteriaGe, criteriaL, criteriaG, } // defines numbers text in the Thai used for the BAHTTEXT formula function. th0 = "\u0E28\u0E39\u0E19\u0E22\u0E4C" th1 = "\u0E2B\u0E19\u0E36\u0E48\u0E07" th2 = "\u0E2A\u0E2D\u0E07" th3 = "\u0E2A\u0E32\u0E21" th4 = "\u0E2A\u0E35\u0E48" th5 = "\u0E2B\u0E49\u0E32" th6 = "\u0E2B\u0E01" th7 = "\u0E40\u0E08\u0E47\u0E14" th8 = "\u0E41\u0E1B\u0E14" th9 = "\u0E40\u0E01\u0E49\u0E32" th10 = "\u0E2A\u0E34\u0E1A" th11 = "\u0E40\u0E2D\u0E47\u0E14" th20 = "\u0E22\u0E35\u0E48" th1e2 = "\u0E23\u0E49\u0E2D\u0E22" th1e3 = "\u0E1E\u0E31\u0E19" th1e4 = "\u0E2B\u0E21\u0E37\u0E48\u0E19" th1e5 = "\u0E41\u0E2A\u0E19" th1e6 = "\u0E25\u0E49\u0E32\u0E19" thDot0 = "\u0E16\u0E49\u0E27\u0E19" thBaht = "\u0E1A\u0E32\u0E17" thSatang = "\u0E2A\u0E15\u0E32\u0E07\u0E04\u0E4C" thMinus = "\u0E25\u0E1A" ) // calcContext defines the formula execution context. type calcContext struct { mu sync.Mutex entry string maxCalcIterations uint iterations map[string]uint iterationsCache map[string]formulaArg } // cellRef defines the structure of a cell reference. type cellRef struct { Col int Row int Sheet string } // cellRef defines the structure of a cell range. type cellRange struct { From cellRef To cellRef } // formulaCriteria defined formula criteria parser result. type formulaCriteria struct { Type byte Condition formulaArg } // ArgType is the type of formula argument type. type ArgType byte // Formula argument types enumeration. const ( ArgUnknown ArgType = iota ArgNumber ArgString ArgList ArgMatrix ArgError ArgEmpty ) // formulaArg is the argument of a formula or function. type formulaArg struct { SheetName string Number float64 String string List []formulaArg Matrix [][]formulaArg Boolean bool Error string Type ArgType cellRefs, cellRanges *list.List } // Value returns a string data type of the formula argument. func (fa formulaArg) Value() (value string) { switch fa.Type { case ArgNumber: if fa.Boolean { if fa.Number == 0 { return "FALSE" } return "TRUE" } return fmt.Sprintf("%g", fa.Number) case ArgString: return fa.String case ArgMatrix: if args := fa.ToList(); len(args) > 0 { return args[0].Value() } case ArgError: return fa.Error } return } // ToNumber returns a formula argument with number data type. func (fa formulaArg) ToNumber() formulaArg { var n float64 var err error switch fa.Type { case ArgString: n, err = strconv.ParseFloat(fa.String, 64) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } case ArgNumber: n = fa.Number case ArgMatrix: if args := fa.ToList(); len(args) > 0 { return args[0].ToNumber() } } return newNumberFormulaArg(n) } // ToBool returns a formula argument with boolean data type. func (fa formulaArg) ToBool() formulaArg { var b bool var err error switch fa.Type { case ArgString: b, err = strconv.ParseBool(fa.String) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } case ArgNumber: if fa.Number == 1 { b = true } } return newBoolFormulaArg(b) } // ToList returns a formula argument with array data type. func (fa formulaArg) ToList() []formulaArg { switch fa.Type { case ArgMatrix: var args []formulaArg for _, row := range fa.Matrix { args = append(args, row...) } return args case ArgList: return fa.List case ArgNumber, ArgString, ArgError, ArgUnknown: return []formulaArg{fa} } return nil } // formulaFuncs is the type of the formula functions. type formulaFuncs struct { f *File ctx *calcContext sheet, cell string } // implicitIntersect applies Excel's implicit intersection to a matrix argument. // For a non-array formula, when a whole-column or whole-row reference is passed // to a scalar function, Excel resolves it to the single cell in the same row // (or column) as the formula cell. If the argument is not a matrix, it is // returned unchanged. func (fn *formulaFuncs) implicitIntersect(arg formulaArg) formulaArg { if arg.Type != ArgMatrix { return arg } _, row, err := CellNameToCoordinates(fn.cell) if err != nil { return arg } // row is 1-based; matrix is 0-indexed idx := row - 1 if idx >= 0 && idx < len(arg.Matrix) && len(arg.Matrix[idx]) > 0 { return arg.Matrix[idx][0] } return arg } // CalcCellValue provides a function to get calculated cell value. This feature // is currently in working processing. Iterative calculation, implicit // intersection, explicit intersection, array formula, table formula and some // other formulas are not supported currently. // // Supported formula functions: // // ABS // ACCRINT // ACCRINTM // ACOS // ACOSH // ACOT // ACOTH // ADDRESS // AGGREGATE // AMORDEGRC // AMORLINC // AND // ARABIC // ARRAYTOTEXT // ASIN // ASINH // ATAN // ATAN2 // ATANH // AVEDEV // AVERAGE // AVERAGEA // AVERAGEIF // AVERAGEIFS // BAHTTEXT // BASE // BESSELI // BESSELJ // BESSELK // BESSELY // BETA.DIST // BETA.INV // BETADIST // BETAINV // BIN2DEC // BIN2HEX // BIN2OCT // BINOM.DIST // BINOM.DIST.RANGE // BINOM.INV // BINOMDIST // BITAND // BITLSHIFT // BITOR // BITRSHIFT // BITXOR // CEILING // CEILING.MATH // CEILING.PRECISE // CHAR // CHIDIST // CHIINV // CHISQ.DIST // CHISQ.DIST.RT // CHISQ.INV // CHISQ.INV.RT // CHISQ.TEST // CHITEST // CHOOSE // CLEAN // CODE // COLUMN // COLUMNS // COMBIN // COMBINA // COMPLEX // CONCAT // CONCATENATE // CONFIDENCE // CONFIDENCE.NORM // CONFIDENCE.T // CONVERT // CORREL // COS // COSH // COT // COTH // COUNT // COUNTA // COUNTBLANK // COUNTIF // COUNTIFS // COUPDAYBS // COUPDAYS // COUPDAYSNC // COUPNCD // COUPNUM // COUPPCD // COVAR // COVARIANCE.P // COVARIANCE.S // CRITBINOM // CSC // CSCH // CUMIPMT // CUMPRINC // DATE // DATEDIF // DATEVALUE // DAVERAGE // DAY // DAYS // DAYS360 // DB // DBCS // DCOUNT // DCOUNTA // DDB // DEC2BIN // DEC2HEX // DEC2OCT // DECIMAL // DEGREES // DELTA // DEVSQ // DGET // DISC // DMAX // DMIN // DOLLAR // DOLLARDE // DOLLARFR // DPRODUCT // DSTDEV // DSTDEVP // DSUM // DURATION // DVAR // DVARP // EDATE // EFFECT // ENCODEURL // EOMONTH // ERF // ERF.PRECISE // ERFC // ERFC.PRECISE // ERROR.TYPE // EUROCONVERT // EVEN // EXACT // EXP // EXPON.DIST // EXPONDIST // F.DIST // F.DIST.RT // F.INV // F.INV.RT // F.TEST // FACT // FACTDOUBLE // FALSE // FDIST // FIND // FINDB // FINV // FISHER // FISHERINV // FIXED // FLOOR // FLOOR.MATH // FLOOR.PRECISE // FORECAST // FORECAST.LINEAR // FORMULATEXT // FREQUENCY // FTEST // FV // FVSCHEDULE // GAMMA // GAMMA.DIST // GAMMA.INV // GAMMADIST // GAMMAINV // GAMMALN // GAMMALN.PRECISE // GAUSS // GCD // GEOMEAN // GESTEP // GROWTH // HARMEAN // HEX2BIN // HEX2DEC // HEX2OCT // HLOOKUP // HOUR // HYPERLINK // HYPGEOM.DIST // HYPGEOMDIST // IF // IFERROR // IFNA // IFS // IMABS // IMAGINARY // IMARGUMENT // IMCONJUGATE // IMCOS // IMCOSH // IMCOT // IMCSC // IMCSCH // IMDIV // IMEXP // IMLN // IMLOG10 // IMLOG2 // IMPOWER // IMPRODUCT // IMREAL // IMSEC // IMSECH // IMSIN // IMSINH // IMSQRT // IMSUB // IMSUM // IMTAN // INDEX // INDIRECT // INT // INTERCEPT // INTRATE // IPMT // IRR // ISBLANK // ISERR // ISERROR // ISEVEN // ISFORMULA // ISLOGICAL // ISNA // ISNONTEXT // ISNUMBER // ISO.CEILING // ISODD // ISOWEEKNUM // ISPMT // ISREF // ISTEXT // KURT // LARGE // LCM // LEFT // LEFTB // LEN // LENB // LN // LOG // LOG10 // LOGINV // LOGNORM.DIST // LOGNORM.INV // LOGNORMDIST // LOOKUP // LOWER // MATCH // MAX // MAXA // MAXIFS // MDETERM // MDURATION // MEDIAN // MID // MIDB // MIN // MINA // MINIFS // MINUTE // MINVERSE // MIRR // MMULT // MOD // MODE // MODE.MULT // MODE.SNGL // MONTH // MROUND // MULTINOMIAL // MUNIT // N // NA // NEGBINOM.DIST // NEGBINOMDIST // NETWORKDAYS // NETWORKDAYS.INTL // NOMINAL // NORM.DIST // NORM.INV // NORM.S.DIST // NORM.S.INV // NORMDIST // NORMINV // NORMSDIST // NORMSINV // NOT // NOW // NPER // NPV // OCT2BIN // OCT2DEC // OCT2HEX // ODD // ODDFPRICE // ODDFYIELD // ODDLPRICE // ODDLYIELD // OR // PDURATION // PEARSON // PERCENTILE // PERCENTILE.EXC // PERCENTILE.INC // PERCENTRANK // PERCENTRANK.EXC // PERCENTRANK.INC // PERMUT // PERMUTATIONA // PHI // PI // PMT // POISSON // POISSON.DIST // POWER // PPMT // PRICE // PRICEDISC // PRICEMAT // PROB // PRODUCT // PROPER // PV // QUARTILE // QUARTILE.EXC // QUARTILE.INC // QUOTIENT // RADIANS // RAND // RANDBETWEEN // RANK // RANK.EQ // RATE // RECEIVED // REPLACE // REPLACEB // REPT // RIGHT // RIGHTB // ROMAN // ROUND // ROUNDDOWN // ROUNDUP // ROW // ROWS // RRI // RSQ // SEARCH // SEARCHB // SEC // SECH // SECOND // SERIESSUM // SHEET // SHEETS // SIGN // SIN // SINH // SKEW // SKEW.P // SLN // SLOPE // SMALL // SORTBY // SQRT // SQRTPI // STANDARDIZE // STDEV // STDEV.P // STDEV.S // STDEVA // STDEVP // STDEVPA // STEYX // SUBSTITUTE // SUBTOTAL // SUM // SUMIF // SUMIFS // SUMPRODUCT // SUMSQ // SUMX2MY2 // SUMX2PY2 // SUMXMY2 // SWITCH // SYD // T // T.DIST // T.DIST.2T // T.DIST.RT // T.INV // T.INV.2T // T.TEST // TAN // TANH // TBILLEQ // TBILLPRICE // TBILLYIELD // TDIST // TEXT // TEXTAFTER // TEXTBEFORE // TEXTJOIN // TIME // TIMEVALUE // TINV // TODAY // TRANSPOSE // TREND // TRIM // TRIMMEAN // TRUE // TRUNC // TTEST // TYPE // UNICHAR // UNICODE // UNIQUE // UPPER // VALUE // VALUETOTEXT // VAR // VAR.P // VAR.S // VARA // VARP // VARPA // VDB // VLOOKUP // WEEKDAY // WEEKNUM // WEIBULL // WEIBULL.DIST // WORKDAY // WORKDAY.INTL // XIRR // XLOOKUP // XNPV // XOR // YEAR // YEARFRAC // YIELD // YIELDDISC // YIELDMAT // Z.TEST // ZTEST func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string, err error) { entry := sheet + "!" + cell if cachedResult, ok := f.calcCache.Load(entry); ok { return cachedResult.(string), nil } options := f.getOptions(opts...) var ( rawCellValue = options.RawCellValue styleIdx int token formulaArg ) if token, err = f.calcCellValue(&calcContext{ entry: entry, maxCalcIterations: options.MaxCalcIterations, iterations: make(map[string]uint), iterationsCache: make(map[string]formulaArg), }, sheet, cell); err != nil { result = token.String return } if !rawCellValue { styleIdx, _ = f.GetCellStyle(sheet, cell) } if token.Type == ArgNumber && !token.Boolean { _, precision, decimal := isNumeric(token.Value()) if precision > 15 { result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'G', 15, 64))}, rawCellValue, CellTypeNumber) if err == nil { f.calcCache.Store(entry, result) } return } if !strings.HasPrefix(result, "0") { result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'f', -1, 64))}, rawCellValue, CellTypeNumber) } if err == nil { f.calcCache.Store(entry, result) } return } result, err = f.formattedValue(&xlsxC{S: styleIdx, V: token.Value()}, rawCellValue, CellTypeInlineString) if err == nil { f.calcCache.Store(entry, result) } return } // clearCalcCache clear all calculation related caches. func (f *File) clearCalcCache() { f.calcCache.Clear() f.formulaArgCache.Clear() } // calcCellValue calculate cell value by given context, worksheet name and cell // reference. func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result formulaArg, err error) { var formula string if formula, err = f.getCellFormula(sheet, cell, true); err != nil { return } ps := efp.ExcelParser() tokens := ps.Parse(formula) if tokens == nil { return f.cellResolver(ctx, sheet, cell) } result, err = f.evalInfixExp(ctx, sheet, cell, tokens) return } // getPriority calculate arithmetic operator priority. func getPriority(token efp.Token) (pri int) { pri = tokenPriority[token.TValue] if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix { pri = 6 } if isBeginParenthesesToken(token) { // ( pri = 0 } return } // newNumberFormulaArg constructs a number formula argument. func newNumberFormulaArg(n float64) formulaArg { if math.IsNaN(n) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return formulaArg{Type: ArgNumber, Number: n} } // newStringFormulaArg constructs a string formula argument. func newStringFormulaArg(s string) formulaArg { return formulaArg{Type: ArgString, String: s} } // newMatrixFormulaArg constructs a matrix formula argument. func newMatrixFormulaArg(m [][]formulaArg) formulaArg { return formulaArg{Type: ArgMatrix, Matrix: m} } // newListFormulaArg create a list formula argument. func newListFormulaArg(l []formulaArg) formulaArg { return formulaArg{Type: ArgList, List: l} } // newBoolFormulaArg constructs a boolean formula argument. func newBoolFormulaArg(b bool) formulaArg { var n float64 if b { n = 1 } return formulaArg{Type: ArgNumber, Number: n, Boolean: true} } // newErrorFormulaArg create an error formula argument of a given type with a // specified error message. func newErrorFormulaArg(formulaError, msg string) formulaArg { return formulaArg{Type: ArgError, String: formulaError, Error: msg} } // newEmptyFormulaArg create an empty formula argument. func newEmptyFormulaArg() formulaArg { return formulaArg{Type: ArgEmpty} } // evalInfixExp evaluate syntax analysis by given infix expression after // lexical analysis. Evaluate an infix expression containing formulas by // stacks: // // opd - Operand // opt - Operator // opf - Operation formula // opfd - Operand of the operation formula // opft - Operator of the operation formula // args - Arguments list of the operation formula // // TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.Token) (formulaArg, error) { var ( err error inArray, inArrayRow bool formulaArray [][]formulaArg formulaArrayRow []formulaArg opdStack, optStack, opfStack = NewStack(), NewStack(), NewStack() opfdStack, opftStack, argsStack = NewStack(), NewStack(), NewStack() ) for i := 0; i < len(tokens); i++ { token := tokens[i] // out of function stack if opfStack.Len() == 0 { if err = f.parseToken(ctx, sheet, token, opdStack, optStack); err != nil { return newEmptyFormulaArg(), err } } // function start if isFunctionStartToken(token) { if token.TValue == "ARRAY" { inArray, formulaArray = true, [][]formulaArg{} continue } if token.TValue == "ARRAYROW" { inArrayRow, formulaArrayRow = true, []formulaArg{} continue } opfStack.Push(token) argsStack.Push(list.New().Init()) opftStack.Push(token) // to know which operators belong to a function use the function as a separator continue } // in function stack, walk 2 token at once if opfStack.Len() > 0 { var nextToken efp.Token if i+1 < len(tokens) { nextToken = tokens[i+1] } // current token is args or range, skip next token, order required: parse reference first if token.TSubType == efp.TokenSubTypeRange { if opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { refTo := f.getDefinedNameRefTo(token.TValue, sheet) if refTo != "" { token.TValue = refTo } // parse reference: must reference at here result, err := f.parseReference(ctx, sheet, token.TValue) if err != nil { return result, err } opfdStack.Push(result) continue } if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction { // parse reference: reference or range at here refTo := f.getDefinedNameRefTo(token.TValue, sheet) if refTo != "" { token.TValue = refTo } result, err := f.parseReference(ctx, sheet, token.TValue) if err != nil { return result, err } // when current token is range, next token is argument and opfdStack not empty, // should push value to opfdStack and continue if nextToken.TType == efp.TokenTypeArgument && !opfdStack.Empty() { opfdStack.Push(result) continue } argsStack.Peek().(*list.List).PushBack(result) continue } } // check current token is opft if err = f.parseToken(ctx, sheet, token, opfdStack, opftStack); err != nil { return newEmptyFormulaArg(), err } // current token is arg if token.TType == efp.TokenTypeArgument { for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { // calculate trigger topOpt := opftStack.Peek().(efp.Token) if err := calculate(opfdStack, topOpt); err != nil { argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error())) } opftStack.Pop() } if !opfdStack.Empty() { argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg)) } continue } if inArrayRow && isOperand(token) { formulaArrayRow = append(formulaArrayRow, opfdStack.Pop().(formulaArg)) continue } if inArrayRow && isFunctionStopToken(token) { formulaArray = append(formulaArray, formulaArrayRow) inArrayRow = false continue } if inArray && isFunctionStopToken(token) { argsStack.Peek().(*list.List).PushBack(newMatrixFormulaArg(formulaArray)) inArray = false continue } if errArg := f.evalInfixExpFunc(ctx, sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); errArg.Type == ArgError { return errArg, errors.New(errArg.Error) } } } for optStack.Len() != 0 { topOpt := optStack.Peek().(efp.Token) if err = calculate(opdStack, topOpt); err != nil { return newEmptyFormulaArg(), err } optStack.Pop() } if opdStack.Len() == 0 { return newEmptyFormulaArg(), ErrInvalidFormula } return opdStack.Peek().(formulaArg), err } // evalInfixExpFunc evaluate formula function in the infix expression. func (f *File) evalInfixExpFunc(ctx *calcContext, sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) formulaArg { if !isFunctionStopToken(token) { return newEmptyFormulaArg() } prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack) // call formula function to evaluate arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell, ctx: ctx}, formulaFnNameReplacer.Replace(opfStack.Peek().(efp.Token).TValue), []reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))}) if arg.Type == ArgError && opfStack.Len() == 1 { return arg } argsStack.Pop() opftStack.Pop() // remove current function separator opfStack.Pop() if opfStack.Len() > 0 { // still in function stack if nextToken.TType == efp.TokenTypeOperatorInfix || opftStack.Len() > 1 { // mathematics calculate in formula function opfdStack.Push(arg) return newEmptyFormulaArg() } argsStack.Peek().(*list.List).PushBack(arg) return newEmptyFormulaArg() } if arg.Type == ArgMatrix && len(arg.Matrix) > 0 && len(arg.Matrix[0]) > 0 { opdStack.Push(arg.Matrix[0][0]) return newEmptyFormulaArg() } opdStack.Push(arg) return newEmptyFormulaArg() } // prepareEvalInfixExp check the token and stack state for formula function // evaluate. func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) { // current token is function stop for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { // calculate trigger topOpt := opftStack.Peek().(efp.Token) if err := calculate(opfdStack, topOpt); err != nil { argsStack.Peek().(*list.List).PushBack(newErrorFormulaArg(err.Error(), err.Error())) opftStack.Pop() continue } opftStack.Pop() } argument := true if opftStack.Len() > 2 && opfdStack.Len() == 1 { topOpt := opftStack.Pop() if opftStack.Peek().(efp.Token).TType == efp.TokenTypeOperatorInfix { argument = false } opftStack.Push(topOpt) } // push opfd to args if argument && opfdStack.Len() > 0 { argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg)) } } // calcPow evaluate exponentiation arithmetic operations. func calcPow(rOpd, lOpd formulaArg, opdStack *Stack) error { lOpdVal := lOpd.ToNumber() if lOpdVal.Type != ArgNumber { return errors.New(lOpdVal.Value()) } rOpdVal := rOpd.ToNumber() if rOpdVal.Type != ArgNumber { return errors.New(rOpdVal.Value()) } opdStack.Push(newNumberFormulaArg(math.Pow(lOpdVal.Number, rOpdVal.Number))) return nil } // calcEq evaluate equal arithmetic operations. func calcEq(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(strings.EqualFold(lOpd.Value(), rOpd.Value()))) return nil } opdStack.Push(newBoolFormulaArg(rOpd.Value() == lOpd.Value())) return nil } // calcNEq evaluate not equal arithmetic operations. func calcNEq(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(!strings.EqualFold(lOpd.Value(), rOpd.Value()))) return nil } opdStack.Push(newBoolFormulaArg(rOpd.Value() != lOpd.Value())) return nil } // calcL evaluate less than arithmetic operations. func calcL(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(lOpd.Number < rOpd.Number)) } if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == -1)) } if rOpd.Type == ArgNumber && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(false)) } if rOpd.Type == ArgString && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(true)) } return nil } // calcLe evaluate less than or equal arithmetic operations. func calcLe(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(lOpd.Number <= rOpd.Number)) } if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != 1)) } if rOpd.Type == ArgNumber && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(false)) } if rOpd.Type == ArgString && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(true)) } return nil } // calcG evaluate greater than arithmetic operations. func calcG(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(lOpd.Number > rOpd.Number)) } if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == 1)) } if rOpd.Type == ArgNumber && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(true)) } if rOpd.Type == ArgString && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(false)) } return nil } // calcGe evaluate greater than or equal arithmetic operations. func calcGe(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(lOpd.Number >= rOpd.Number)) } if rOpd.Type == ArgString && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != -1)) } if rOpd.Type == ArgNumber && lOpd.Type == ArgString { opdStack.Push(newBoolFormulaArg(true)) } if rOpd.Type == ArgString && lOpd.Type == ArgNumber { opdStack.Push(newBoolFormulaArg(false)) } return nil } // calcSplice evaluate splice '&' operations. func calcSplice(rOpd, lOpd formulaArg, opdStack *Stack) error { opdStack.Push(newStringFormulaArg(lOpd.Value() + rOpd.Value())) return nil } // calcAdd evaluate addition arithmetic operations. func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error { lOpdVal := lOpd.ToNumber() if lOpdVal.Type != ArgNumber { return errors.New(lOpdVal.Value()) } rOpdVal := rOpd.ToNumber() if rOpdVal.Type != ArgNumber { return errors.New(rOpdVal.Value()) } opdStack.Push(newNumberFormulaArg(lOpdVal.Number + rOpdVal.Number)) return nil } // calcSubtract evaluate subtraction arithmetic operations. func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error { if rOpd.Value() == "" { rOpd = newNumberFormulaArg(0) } if lOpd.Value() == "" { lOpd = newNumberFormulaArg(0) } lOpdVal := lOpd.ToNumber() if lOpdVal.Type != ArgNumber { return errors.New(lOpdVal.Value()) } rOpdVal := rOpd.ToNumber() if rOpdVal.Type != ArgNumber { return errors.New(rOpdVal.Value()) } opdStack.Push(newNumberFormulaArg(lOpdVal.Number - rOpdVal.Number)) return nil } // calcMultiply evaluate multiplication arithmetic operations. func calcMultiply(rOpd, lOpd formulaArg, opdStack *Stack) error { lOpdVal := lOpd.ToNumber() if lOpdVal.Type != ArgNumber { return errors.New(lOpdVal.Value()) } rOpdVal := rOpd.ToNumber() if rOpdVal.Type != ArgNumber { return errors.New(rOpdVal.Value()) } opdStack.Push(newNumberFormulaArg(lOpdVal.Number * rOpdVal.Number)) return nil } // calcDiv evaluate division arithmetic operations. func calcDiv(rOpd, lOpd formulaArg, opdStack *Stack) error { lOpdVal := lOpd.ToNumber() if lOpdVal.Type != ArgNumber { return errors.New(lOpdVal.Value()) } rOpdVal := rOpd.ToNumber() if rOpdVal.Type != ArgNumber { return errors.New(rOpdVal.Value()) } if rOpdVal.Number == 0 { return errors.New(formulaErrorDIV) } opdStack.Push(newNumberFormulaArg(lOpdVal.Number / rOpdVal.Number)) return nil } // calculate evaluate basic arithmetic operations. func calculate(opdStack *Stack, opt efp.Token) error { if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorPrefix { if opdStack.Len() < 1 { return ErrInvalidFormula } opd := opdStack.Pop().(formulaArg) opdStack.Push(newNumberFormulaArg(0 - opd.ToNumber().Number)) } if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix { if opdStack.Len() < 2 { return ErrInvalidFormula } rOpd := opdStack.Pop().(formulaArg) lOpd := opdStack.Pop().(formulaArg) if err := calcSubtract(rOpd, lOpd, opdStack); err != nil { return err } } tokenCalcFunc := map[string]func(rOpd, lOpd formulaArg, opdStack *Stack) error{ "^": calcPow, "*": calcMultiply, "/": calcDiv, "+": calcAdd, "=": calcEq, "<>": calcNEq, "<": calcL, "<=": calcLe, ">": calcG, ">=": calcGe, "&": calcSplice, } if fn, ok := tokenCalcFunc[opt.TValue]; ok { if opdStack.Len() < 2 { return ErrInvalidFormula } rOpd := opdStack.Pop().(formulaArg) lOpd := opdStack.Pop().(formulaArg) if opt.TValue != "&" { if rOpd.Value() == "" { rOpd = newNumberFormulaArg(0) } if lOpd.Value() == "" { lOpd = newNumberFormulaArg(0) } } if rOpd.Type == ArgError { return errors.New(rOpd.Value()) } if lOpd.Type == ArgError { return errors.New(lOpd.Value()) } return fn(rOpd, lOpd, opdStack) } return nil } // parseOperatorPrefixToken parse operator prefix token. func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Token) (err error) { if optStack.Len() == 0 { optStack.Push(token) return } tokenPriority := getPriority(token) topOpt := optStack.Peek().(efp.Token) topOptPriority := getPriority(topOpt) if topOpt.TValue == "-" && topOpt.TType == efp.TokenTypeOperatorPrefix && token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix { optStack.Pop() return } if tokenPriority > topOptPriority { optStack.Push(token) return } for tokenPriority <= topOptPriority { optStack.Pop() if err = calculate(opdStack, topOpt); err != nil { return } if optStack.Len() > 0 { topOpt = optStack.Peek().(efp.Token) topOptPriority = getPriority(topOpt) continue } break } optStack.Push(token) return } // isFunctionStartToken determine if the token is function start. func isFunctionStartToken(token efp.Token) bool { return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart } // isFunctionStopToken determine if the token is function stop. func isFunctionStopToken(token efp.Token) bool { return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop } // isBeginParenthesesToken determine if the token is begin parentheses: (. func isBeginParenthesesToken(token efp.Token) bool { return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart } // isEndParenthesesToken determine if the token is end parentheses: ). func isEndParenthesesToken(token efp.Token) bool { return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop } // isOperatorPrefixToken determine if the token is parse operator prefix // token. func isOperatorPrefixToken(token efp.Token) bool { _, ok := tokenPriority[token.TValue] return (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix) } // isOperand determine if the token is parse operand. func isOperand(token efp.Token) bool { return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText || token.TSubType == efp.TokenSubTypeLogical) } // tokenToFormulaArg create a formula argument by given token. func tokenToFormulaArg(token efp.Token) formulaArg { switch token.TSubType { case efp.TokenSubTypeLogical: return newBoolFormulaArg(strings.EqualFold(token.TValue, "TRUE")) case efp.TokenSubTypeNumber: num, _ := strconv.ParseFloat(token.TValue, 64) return newNumberFormulaArg(num) default: return newStringFormulaArg(token.TValue) } } // formulaArgToToken create a token by given formula argument. func formulaArgToToken(arg formulaArg) efp.Token { switch arg.Type { case ArgNumber: if arg.Boolean { return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeLogical} } return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber} default: return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeText} } } // parseToken parse basic arithmetic operator priority and evaluate based on // operators and operands. func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdStack, optStack *Stack) error { // parse reference: must reference at here if token.TSubType == efp.TokenSubTypeRange { refTo := f.getDefinedNameRefTo(token.TValue, sheet) if refTo != "" { token.TValue = refTo } result, err := f.parseReference(ctx, sheet, token.TValue) if err != nil { return errors.New(formulaErrorNAME) } token = formulaArgToToken(result) } if isOperatorPrefixToken(token) { if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil { return err } } if isBeginParenthesesToken(token) { // ( optStack.Push(token) } if isEndParenthesesToken(token) { // ) for !isBeginParenthesesToken(optStack.Peek().(efp.Token)) { // != ( topOpt := optStack.Peek().(efp.Token) if err := calculate(opdStack, topOpt); err != nil { return err } optStack.Pop() } optStack.Pop() } if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() { topOpd := opdStack.Pop().(formulaArg) opdStack.Push(newNumberFormulaArg(topOpd.Number / 100)) } // opd if isOperand(token) { opdStack.Push(tokenToFormulaArg(token)) } return nil } // parseRef parse reference for a cell, column name or row number. func parseRef(ref string) (cellRef, bool, bool, error) { var ( err, colErr, rowErr error cr cellRef cell = ref tokens = strings.Split(ref, "!") ) if len(tokens) == 2 { // have a worksheet cr.Sheet, cell = strings.TrimSuffix(strings.TrimPrefix(tokens[0], "'"), "'"), tokens[1] } if cr.Col, cr.Row, err = CellNameToCoordinates(cell); err != nil { if cr.Col, colErr = ColumnNameToNumber(cell); colErr == nil { // cast to column return cr, true, false, nil } if cr.Row, rowErr = strconv.Atoi(cell); rowErr == nil { // cast to row if cr.Row < 1 || cr.Row > TotalRows { return cr, false, false, err } return cr, false, true, nil } return cr, false, false, err } return cr, false, false, err } // prepareCellRange checking and convert cell reference to a cell range. func (cr *cellRange) prepareCellRange(col, row bool, cellRef cellRef) error { if col { cellRef.Row = TotalRows } if row { cellRef.Col = MaxColumns } if cellRef.Sheet == "" { cellRef.Sheet = cr.From.Sheet } if cr.From.Sheet != cellRef.Sheet || cr.To.Sheet != cellRef.Sheet { return errors.New("invalid reference") } if cr.From.Col > cellRef.Col { cr.From.Col = cellRef.Col } if cr.From.Row > cellRef.Row { cr.From.Row = cellRef.Row } if cr.To.Col < cellRef.Col { cr.To.Col = cellRef.Col } if cr.To.Row < cellRef.Row { cr.To.Row = cellRef.Row } return nil } // parseReference parse reference and extract values by given reference // characters and default sheet name. func (f *File) parseReference(ctx *calcContext, sheet, reference string) (formulaArg, error) { reference = strings.ReplaceAll(reference, "$", "") ranges, cellRanges, cellRefs := strings.Split(reference, ":"), list.New(), list.New() if len(ranges) > 1 { var cr cellRange for i, ref := range ranges { cellRef, col, row, err := parseRef(ref) if err != nil { return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference") } if i == 0 { if col { cellRef.Row = 1 } if row { cellRef.Col = 1 } if cellRef.Sheet == "" { cellRef.Sheet = sheet } cr.From, cr.To = cellRef, cellRef continue } if err := cr.prepareCellRange(col, row, cellRef); err != nil { return newErrorFormulaArg(formulaErrorNAME, err.Error()), err } } cellRanges.PushBack(cr) return f.rangeResolver(ctx, cellRefs, cellRanges) } cellRef, _, _, err := parseRef(reference) if err != nil { return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference") } if cellRef.Sheet == "" { cellRef.Sheet = sheet } cellRefs.PushBack(cellRef) return f.rangeResolver(ctx, cellRefs, cellRanges) } // prepareValueRange prepare value range. func prepareValueRange(cr cellRange, valueRange []int) { if cr.From.Row < valueRange[0] || valueRange[0] == 0 { valueRange[0] = cr.From.Row } if cr.From.Col < valueRange[2] || valueRange[2] == 0 { valueRange[2] = cr.From.Col } if cr.To.Row > valueRange[1] || valueRange[1] == 0 { valueRange[1] = cr.To.Row } if cr.To.Col > valueRange[3] || valueRange[3] == 0 { valueRange[3] = cr.To.Col } } // prepareValueRef prepare value reference. func prepareValueRef(cr cellRef, valueRange []int) { if cr.Row < valueRange[0] || valueRange[0] == 0 { valueRange[0] = cr.Row } if cr.Col < valueRange[2] || valueRange[2] == 0 { valueRange[2] = cr.Col } if cr.Row > valueRange[1] || valueRange[1] == 0 { valueRange[1] = cr.Row } if cr.Col > valueRange[3] || valueRange[3] == 0 { valueRange[3] = cr.Col } } // cellResolver calc cell value by given worksheet name, cell reference and context. func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, error) { var ( arg formulaArg value string err error ) ref := sheet + "!" + cell if cached, ok := f.formulaArgCache.Load(ref); ok { return cached.(formulaArg), err } if formula, _ := f.getCellFormula(sheet, cell, true); len(formula) != 0 { ctx.mu.Lock() if ctx.entry != ref { if ctx.iterations[ref] <= f.options.MaxCalcIterations { ctx.iterations[ref]++ ctx.mu.Unlock() arg, _ = f.calcCellValue(ctx, sheet, cell) ctx.iterationsCache[ref] = arg f.formulaArgCache.Store(ref, arg) return arg, nil } ctx.mu.Unlock() return ctx.iterationsCache[ref], nil } ctx.mu.Unlock() } if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil { return arg, err } arg = newStringFormulaArg(value) cellType, _ := f.GetCellType(sheet, cell) switch cellType { case CellTypeBool: arg = arg.ToBool() case CellTypeNumber, CellTypeUnset: if arg.Value() == "" { arg = newEmptyFormulaArg() } else { arg = arg.ToNumber() } case CellTypeInlineString, CellTypeSharedString: case CellTypeFormula: if value == "" { arg = newEmptyFormulaArg() } case CellTypeDate: if value, err = f.GetCellValue(sheet, cell); err == nil { if num := newStringFormulaArg(value).ToNumber(); num.Type == ArgNumber { arg = num } } default: arg = newErrorFormulaArg(value, value) } f.formulaArgCache.Store(ref, arg) return arg, err } // rangeResolver extract value as string from given reference and range list. // This function will not ignore the empty cell. For example, A1:A2:A2:B3 will // be reference A1:B3. func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List) (arg formulaArg, err error) { arg.cellRefs, arg.cellRanges = cellRefs, cellRanges // value range order: from row, to row, from column, to column valueRange := []int{0, 0, 0, 0} var sheet string // prepare value range for temp := cellRanges.Front(); temp != nil; temp = temp.Next() { cr := temp.Value.(cellRange) rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row} _ = sortCoordinates(rng) cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row = rng[0], rng[1], rng[2], rng[3] prepareValueRange(cr, valueRange) if cr.From.Sheet != "" { sheet = cr.From.Sheet } } for temp := cellRefs.Front(); temp != nil; temp = temp.Next() { cr := temp.Value.(cellRef) if cr.Sheet != "" { sheet = cr.Sheet } prepareValueRef(cr, valueRange) } // extract value from ranges if cellRanges.Len() > 0 { arg.Type = ArgMatrix var ws *xlsxWorksheet ws, err = f.workSheetReader(sheet) if err != nil { return } // Detect whole column/row reference, limit to actual data range if valueRange[1] == TotalRows { actualMaxRow := 0 for _, rowData := range ws.SheetData.Row { if rowData.R > actualMaxRow { actualMaxRow = rowData.R } } if actualMaxRow > 0 && actualMaxRow < TotalRows { valueRange[1] = actualMaxRow } } if valueRange[3] == MaxColumns { actualMaxCol := 0 for _, rowData := range ws.SheetData.Row { for _, cell := range rowData.C { col, _, err := CellNameToCoordinates(cell.R) if err == nil && col > actualMaxCol { actualMaxCol = col } } } if actualMaxCol > 0 && actualMaxCol < MaxColumns { valueRange[3] = actualMaxCol } } for row := valueRange[0]; row <= valueRange[1]; row++ { colMax := 0 if 0 < row && row <= len(ws.SheetData.Row) { rowData := &ws.SheetData.Row[row-1] colMax = min(valueRange[3], len(rowData.C)) } var matrixRow []formulaArg for col := valueRange[2]; col <= valueRange[3]; col++ { value := newEmptyFormulaArg() if col <= colMax { var cell string if cell, err = CoordinatesToCellName(col, row); err != nil { return } if value, err = f.cellResolver(ctx, sheet, cell); err != nil { return } } matrixRow = append(matrixRow, value) } arg.Matrix = append(arg.Matrix, matrixRow) } return } // extract value from references for temp := cellRefs.Front(); temp != nil; temp = temp.Next() { cr := temp.Value.(cellRef) var cell string if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil { return } if arg, err = f.cellResolver(ctx, cr.Sheet, cell); err != nil { return } arg.cellRefs, arg.cellRanges = cellRefs, cellRanges } return } // callFuncByName calls the no error or only error return function with // reflect by given receiver, name and parameters. func callFuncByName(receiver interface{}, name string, params []reflect.Value) (arg formulaArg) { function := reflect.ValueOf(receiver).MethodByName(name) if function.IsValid() { rt := function.Call(params) if len(rt) == 0 { return } arg = rt[0].Interface().(formulaArg) return } return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("not support %s function", name)) } // formulaCriteriaParser parse formula criteria. func formulaCriteriaParser(exp formulaArg) *formulaCriteria { prepareValue := func(cond string) (expected float64, err error) { percentile := 1.0 if strings.HasSuffix(cond, "%") { cond = strings.TrimSuffix(cond, "%") percentile /= 100 } if expected, err = strconv.ParseFloat(cond, 64); err != nil { return } expected *= percentile return } fc, val := &formulaCriteria{}, exp.Value() if val == "" { return fc } for i, re := range formulaFormats { if match := re.FindStringSubmatch(val); len(match) > 1 { fc.Condition = newStringFormulaArg(match[1]) if num, err := prepareValue(match[1]); err == nil { fc.Condition = newNumberFormulaArg(num) } fc.Type = formulaCriterias[i] return fc } } hasWildcard := false pattern := wildcardTokenRE.ReplaceAllStringFunc(val, func(m string) string { hasWildcard = hasWildcard || wildcardBareTokens[m] if r, ok := wildcardPatternMap[m]; ok { return r } return regexp.QuoteMeta(m) }) if hasWildcard { fc.Type, fc.Condition = criteriaRegexp, newStringFormulaArg("(?i)^"+pattern+"$") return fc } fc.Type, fc.Condition = criteriaEq, newStringFormulaArg( strings.NewReplacer("~~", "~", "~*", "*", "~?", "?").Replace(val), ) if num := fc.Condition.ToNumber(); num.Type == ArgNumber { fc.Condition = num } return fc } // formulaCriteriaEval evaluate formula criteria expression. func formulaCriteriaEval(val formulaArg, criteria *formulaCriteria) (result bool, err error) { s := NewStack() tokenCalcFunc := map[byte]func(rOpd, lOpd formulaArg, opdStack *Stack) error{ criteriaEq: calcEq, criteriaNe: calcNEq, criteriaL: calcL, criteriaLe: calcLe, criteriaG: calcG, criteriaGe: calcGe, } switch criteria.Type { case criteriaEq, criteriaLe, criteriaGe, criteriaNe, criteriaL, criteriaG: if fn, ok := tokenCalcFunc[criteria.Type]; ok { if _ = fn(criteria.Condition, val, s); s.Len() > 0 { return s.Pop().(formulaArg).Number == 1, err } } case criteriaRegexp: pattern := criteria.Condition.Value() if !strings.HasPrefix(pattern, "^") { pattern = "^" + pattern } if !strings.HasSuffix(pattern, "$") { pattern = pattern + "$" } return regexp.MatchString(pattern, val.Value()) } return } // Engineering Functions // BESSELI function the modified Bessel function, which is equivalent to the // Bessel function evaluated for purely imaginary arguments. The syntax of // the Besseli function is: // // BESSELI(x,n) func (fn *formulaFuncs) BESSELI(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "BESSELI requires 2 numeric arguments") } return fn.bassel(argsList, true) } // BESSELJ function returns the Bessel function, Jn(x), for a specified order // and value of x. The syntax of the function is: // // BESSELJ(x,n) func (fn *formulaFuncs) BESSELJ(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "BESSELJ requires 2 numeric arguments") } return fn.bassel(argsList, false) } // bassel is an implementation of the formula functions BESSELI and BESSELJ. func (fn *formulaFuncs) bassel(argsList *list.List, modfied bool) formulaArg { x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } if n.Type != ArgNumber { return n } maxVal, x1 := 100, x.Number*0.5 x2 := x1 * x1 x1 = math.Pow(x1, n.Number) n1, n2, n3, n4, add := fact(n.Number), 1.0, 0.0, n.Number, false result := x1 / n1 t := result * 0.9 for result != t && maxVal != 0 { x1 *= x2 n3++ n1 *= n3 n4++ n2 *= n4 t = result r := x1 / n1 / n2 if modfied || add { result += r } else { result -= r } maxVal-- add = !add } return newNumberFormulaArg(result) } // BESSELK function calculates the modified Bessel functions, Kn(x), which are // also known as the hyperbolic Bessel Functions. These are the equivalent of // the Bessel functions, evaluated for purely imaginary arguments. The syntax // of the function is: // // BESSELK(x,n) func (fn *formulaFuncs) BESSELK(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "BESSELK requires 2 numeric arguments") } x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } if n.Type != ArgNumber { return n } if x.Number <= 0 || n.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } var result float64 switch math.Floor(n.Number) { case 0: result = fn.besselK0(x) case 1: result = fn.besselK1(x) default: result = fn.besselK2(x, n) } return newNumberFormulaArg(result) } // besselK0 is an implementation of the formula function BESSELK. func (fn *formulaFuncs) besselK0(x formulaArg) float64 { var y float64 if x.Number <= 2 { n2 := x.Number * 0.5 y = n2 * n2 args := list.New() args.PushBack(x) args.PushBack(newNumberFormulaArg(0)) return -math.Log(n2)*fn.BESSELI(args).Number + (-0.57721566 + y*(0.42278420+y*(0.23069756+y*(0.3488590e-1+y*(0.262698e-2+y* (0.10750e-3+y*0.74e-5)))))) } y = 2 / x.Number return math.Exp(-x.Number) / math.Sqrt(x.Number) * (1.25331414 + y*(-0.7832358e-1+y*(0.2189568e-1+y*(-0.1062446e-1+y* (0.587872e-2+y*(-0.251540e-2+y*0.53208e-3)))))) } // besselK1 is an implementation of the formula function BESSELK. func (fn *formulaFuncs) besselK1(x formulaArg) float64 { var n2, y float64 if x.Number <= 2 { n2 = x.Number * 0.5 y = n2 * n2 args := list.New() args.PushBack(x) args.PushBack(newNumberFormulaArg(1)) return math.Log(n2)*fn.BESSELI(args).Number + (1+y*(0.15443144+y*(-0.67278579+y*(-0.18156897+y*(-0.1919402e-1+y*(-0.110404e-2+y*(-0.4686e-4)))))))/x.Number } y = 2 / x.Number return math.Exp(-x.Number) / math.Sqrt(x.Number) * (1.25331414 + y*(0.23498619+y*(-0.3655620e-1+y*(0.1504268e-1+y*(-0.780353e-2+y* (0.325614e-2+y*(-0.68245e-3))))))) } // besselK2 is an implementation of the formula function BESSELK. func (fn *formulaFuncs) besselK2(x, n formulaArg) float64 { tox, bkm, bk, bkp := 2/x.Number, fn.besselK0(x), fn.besselK1(x), 0.0 for i := 1.0; i < n.Number; i++ { bkp = math.FMA(i*tox, bk, bkm) bkm = bk bk = bkp } return bk } // BESSELY function returns the Bessel function, Yn(x), (also known as the // Weber function or the Neumann function), for a specified order and value // of x. The syntax of the function is: // // BESSELY(x,n) func (fn *formulaFuncs) BESSELY(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "BESSELY requires 2 numeric arguments") } x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } if n.Type != ArgNumber { return n } if x.Number <= 0 || n.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } var result float64 switch math.Floor(n.Number) { case 0: result = fn.besselY0(x) case 1: result = fn.besselY1(x) default: result = fn.besselY2(x, n) } return newNumberFormulaArg(result) } // besselY0 is an implementation of the formula function BESSELY. func (fn *formulaFuncs) besselY0(x formulaArg) float64 { var y float64 if x.Number < 8 { y = x.Number * x.Number f1 := -2957821389.0 + y*(7062834065.0+y*(-512359803.6+y*(10879881.29+y* (-86327.92757+y*228.4622733)))) f2 := 40076544269.0 + y*(745249964.8+y*(7189466.438+y* (47447.26470+y*(226.1030244+y)))) args := list.New() args.PushBack(x) args.PushBack(newNumberFormulaArg(0)) return f1/f2 + 0.636619772*fn.BESSELJ(args).Number*math.Log(x.Number) } z := 8.0 / x.Number y = z * z xx := x.Number - 0.785398164 f1 := 1 + y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6))) f2 := -0.1562499995e-1 + y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6+y* (-0.934945152e-7)))) return math.Sqrt(0.636619772/x.Number) * (math.Sin(xx)*f1 + z*math.Cos(xx)*f2) } // besselY1 is an implementation of the formula function BESSELY. func (fn *formulaFuncs) besselY1(x formulaArg) float64 { if x.Number < 8 { y := x.Number * x.Number f1 := x.Number * (-0.4900604943e13 + y*(0.1275274390e13+y*(-0.5153438139e11+y* (0.7349264551e9+y*(-0.4237922726e7+y*0.8511937935e4))))) f2 := 0.2499580570e14 + y*(0.4244419664e12+y*(0.3733650367e10+y*(0.2245904002e8+y* (0.1020426050e6+y*(0.3549632885e3+y))))) args := list.New() args.PushBack(x) args.PushBack(newNumberFormulaArg(1)) return f1/f2 + 0.636619772*(fn.BESSELJ(args).Number*math.Log(x.Number)-1/x.Number) } return math.Sqrt(0.636619772/x.Number) * math.Sin(x.Number-2.356194491) } // besselY2 is an implementation of the formula function BESSELY. func (fn *formulaFuncs) besselY2(x, n formulaArg) float64 { tox, bym, by, byp := 2/x.Number, fn.besselY0(x), fn.besselY1(x), 0.0 for i := 1.0; i < n.Number; i++ { byp = math.FMA(i*tox, by, -bym) bym = by by = byp } return by } // BIN2DEC function converts a Binary (a base-2 number) into a decimal number. // The syntax of the function is: // // BIN2DEC(number) func (fn *formulaFuncs) BIN2DEC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "BIN2DEC requires 1 numeric argument") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } return fn.bin2dec(token.Value()) } // BIN2HEX function converts a Binary (Base 2) number into a Hexadecimal // (Base 16) number. The syntax of the function is: // // BIN2HEX(number,[places]) func (fn *formulaFuncs) BIN2HEX(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX allows at most 2 arguments") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } decimal, newList := fn.bin2dec(token.Value()), list.New() if decimal.Type != ArgNumber { return decimal } newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("BIN2HEX", newList) } // BIN2OCT function converts a Binary (Base 2) number into an Octal (Base 8) // number. The syntax of the function is: // // BIN2OCT(number,[places]) func (fn *formulaFuncs) BIN2OCT(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT allows at most 2 arguments") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } decimal, newList := fn.bin2dec(token.Value()), list.New() if decimal.Type != ArgNumber { return decimal } newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("BIN2OCT", newList) } // bin2dec is an implementation of the formula function BIN2DEC. func (fn *formulaFuncs) bin2dec(number string) formulaArg { decimal, length := 0.0, len(number) for i := length; i > 0; i-- { s := string(number[length-i]) if i == 10 && s == "1" { decimal += math.Pow(-2.0, float64(i-1)) continue } if s == "1" { decimal += math.Pow(2.0, float64(i-1)) continue } if s != "0" { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newNumberFormulaArg(decimal) } // BITAND function returns the bitwise 'AND' for two supplied integers. The // syntax of the function is: // // BITAND(number1,number2) func (fn *formulaFuncs) BITAND(argsList *list.List) formulaArg { return fn.bitwise("BITAND", argsList) } // BITLSHIFT function returns a supplied integer, shifted left by a specified // number of bits. The syntax of the function is: // // BITLSHIFT(number1,shift_amount) func (fn *formulaFuncs) BITLSHIFT(argsList *list.List) formulaArg { return fn.bitwise("BITLSHIFT", argsList) } // BITOR function returns the bitwise 'OR' for two supplied integers. The // syntax of the function is: // // BITOR(number1,number2) func (fn *formulaFuncs) BITOR(argsList *list.List) formulaArg { return fn.bitwise("BITOR", argsList) } // BITRSHIFT function returns a supplied integer, shifted right by a specified // number of bits. The syntax of the function is: // // BITRSHIFT(number1,shift_amount) func (fn *formulaFuncs) BITRSHIFT(argsList *list.List) formulaArg { return fn.bitwise("BITRSHIFT", argsList) } // BITXOR function returns the bitwise 'XOR' (exclusive 'OR') for two supplied // integers. The syntax of the function is: // // BITXOR(number1,number2) func (fn *formulaFuncs) BITXOR(argsList *list.List) formulaArg { return fn.bitwise("BITXOR", argsList) } // bitwise is an implementation of the formula functions BITAND, BITLSHIFT, // BITOR, BITRSHIFT and BITXOR. func (fn *formulaFuncs) bitwise(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 numeric arguments", name)) } num1, num2 := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() if num1.Type != ArgNumber || num2.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } maxVal := math.Pow(2, 48) - 1 if num1.Number < 0 || num1.Number > maxVal || num2.Number < 0 || num2.Number > maxVal { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } bitwiseFuncMap := map[string]func(a, b int) int{ "BITAND": func(a, b int) int { return a & b }, "BITLSHIFT": func(a, b int) int { return a << uint(b) }, "BITOR": func(a, b int) int { return a | b }, "BITRSHIFT": func(a, b int) int { return a >> uint(b) }, "BITXOR": func(a, b int) int { return a ^ b }, } bitwiseFunc := bitwiseFuncMap[name] return newNumberFormulaArg(float64(bitwiseFunc(int(num1.Number), int(num2.Number)))) } // COMPLEX function takes two arguments, representing the real and the // imaginary coefficients of a complex number, and from these, creates a // complex number. The syntax of the function is: // // COMPLEX(real_num,i_num,[suffix]) func (fn *formulaFuncs) COMPLEX(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX requires at least 2 arguments") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX allows at most 3 arguments") } realNum, i, suffix := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber(), "i" if realNum.Type != ArgNumber { return realNum } if i.Type != ArgNumber { return i } if argsList.Len() == 3 { if suffix = strings.ToLower(argsList.Back().Value.(formulaArg).Value()); suffix != "i" && suffix != "j" { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } return newStringFormulaArg(cmplx2str(complex(realNum.Number, i.Number), suffix)) } // cmplx2str replace complex number string characters. func cmplx2str(num complex128, suffix string) string { realPart, imagPart := fmt.Sprint(real(num)), fmt.Sprint(imag(num)) isNum, i, decimal := isNumeric(realPart) if isNum && i > 15 { realPart = strconv.FormatFloat(decimal, 'G', 15, 64) } isNum, i, decimal = isNumeric(imagPart) if isNum && i > 15 { imagPart = strconv.FormatFloat(decimal, 'G', 15, 64) } c := realPart if imag(num) > 0 { c += "+" } if imag(num) != 0 { c += imagPart + "i" } c = strings.TrimPrefix(c, "(") c = strings.TrimPrefix(c, "+0+") c = strings.TrimPrefix(c, "-0+") c = strings.TrimSuffix(c, ")") c = strings.TrimPrefix(c, "0+") if strings.HasPrefix(c, "0-") { c = "-" + strings.TrimPrefix(c, "0-") } c = strings.TrimPrefix(c, "0+") c = strings.TrimSuffix(c, "+0i") c = strings.TrimSuffix(c, "-0i") c = strings.NewReplacer("+1i", "+i", "-1i", "-i").Replace(c) c = strings.ReplaceAll(c, "i", suffix) return c } // str2cmplx convert complex number string characters. func str2cmplx(c string) string { c = strings.ReplaceAll(c, "j", "i") if c == "i" { c = "1i" } c = strings.NewReplacer("+i", "+1i", "-i", "-1i").Replace(c) return c } // conversionUnit defined unit info for conversion. type conversionUnit struct { group uint8 allowPrefix bool } // conversionUnits maps info list for unit conversion, that can be used in // formula function CONVERT. var conversionUnits = map[string]conversionUnit{ // weight and mass "g": {group: categoryWeightAndMass, allowPrefix: true}, "sg": {group: categoryWeightAndMass, allowPrefix: false}, "lbm": {group: categoryWeightAndMass, allowPrefix: false}, "u": {group: categoryWeightAndMass, allowPrefix: true}, "ozm": {group: categoryWeightAndMass, allowPrefix: false}, "grain": {group: categoryWeightAndMass, allowPrefix: false}, "cwt": {group: categoryWeightAndMass, allowPrefix: false}, "shweight": {group: categoryWeightAndMass, allowPrefix: false}, "uk_cwt": {group: categoryWeightAndMass, allowPrefix: false}, "lcwt": {group: categoryWeightAndMass, allowPrefix: false}, "hweight": {group: categoryWeightAndMass, allowPrefix: false}, "stone": {group: categoryWeightAndMass, allowPrefix: false}, "ton": {group: categoryWeightAndMass, allowPrefix: false}, "uk_ton": {group: categoryWeightAndMass, allowPrefix: false}, "LTON": {group: categoryWeightAndMass, allowPrefix: false}, "brton": {group: categoryWeightAndMass, allowPrefix: false}, // distance "m": {group: categoryDistance, allowPrefix: true}, "mi": {group: categoryDistance, allowPrefix: false}, "Nmi": {group: categoryDistance, allowPrefix: false}, "in": {group: categoryDistance, allowPrefix: false}, "ft": {group: categoryDistance, allowPrefix: false}, "yd": {group: categoryDistance, allowPrefix: false}, "ang": {group: categoryDistance, allowPrefix: true}, "ell": {group: categoryDistance, allowPrefix: false}, "ly": {group: categoryDistance, allowPrefix: false}, "parsec": {group: categoryDistance, allowPrefix: false}, "pc": {group: categoryDistance, allowPrefix: false}, "Pica": {group: categoryDistance, allowPrefix: false}, "Picapt": {group: categoryDistance, allowPrefix: false}, "pica": {group: categoryDistance, allowPrefix: false}, "survey_mi": {group: categoryDistance, allowPrefix: false}, // time "yr": {group: categoryTime, allowPrefix: false}, "day": {group: categoryTime, allowPrefix: false}, "d": {group: categoryTime, allowPrefix: false}, "hr": {group: categoryTime, allowPrefix: false}, "mn": {group: categoryTime, allowPrefix: false}, "min": {group: categoryTime, allowPrefix: false}, "sec": {group: categoryTime, allowPrefix: true}, "s": {group: categoryTime, allowPrefix: true}, // pressure "Pa": {group: categoryPressure, allowPrefix: true}, "p": {group: categoryPressure, allowPrefix: true}, "atm": {group: categoryPressure, allowPrefix: true}, "at": {group: categoryPressure, allowPrefix: true}, "mmHg": {group: categoryPressure, allowPrefix: true}, "psi": {group: categoryPressure, allowPrefix: true}, "Torr": {group: categoryPressure, allowPrefix: true}, // force "N": {group: categoryForce, allowPrefix: true}, "dyn": {group: categoryForce, allowPrefix: true}, "dy": {group: categoryForce, allowPrefix: true}, "lbf": {group: categoryForce, allowPrefix: false}, "pond": {group: categoryForce, allowPrefix: true}, // energy "J": {group: categoryEnergy, allowPrefix: true}, "e": {group: categoryEnergy, allowPrefix: true}, "c": {group: categoryEnergy, allowPrefix: true}, "cal": {group: categoryEnergy, allowPrefix: true}, "eV": {group: categoryEnergy, allowPrefix: true}, "ev": {group: categoryEnergy, allowPrefix: true}, "HPh": {group: categoryEnergy, allowPrefix: false}, "hh": {group: categoryEnergy, allowPrefix: false}, "Wh": {group: categoryEnergy, allowPrefix: true}, "wh": {group: categoryEnergy, allowPrefix: true}, "flb": {group: categoryEnergy, allowPrefix: false}, "BTU": {group: categoryEnergy, allowPrefix: false}, "btu": {group: categoryEnergy, allowPrefix: false}, // power "HP": {group: categoryPower, allowPrefix: false}, "h": {group: categoryPower, allowPrefix: false}, "W": {group: categoryPower, allowPrefix: true}, "w": {group: categoryPower, allowPrefix: true}, "PS": {group: categoryPower, allowPrefix: false}, "T": {group: categoryMagnetism, allowPrefix: true}, "ga": {group: categoryMagnetism, allowPrefix: true}, // temperature "C": {group: categoryTemperature, allowPrefix: false}, "cel": {group: categoryTemperature, allowPrefix: false}, "F": {group: categoryTemperature, allowPrefix: false}, "fah": {group: categoryTemperature, allowPrefix: false}, "K": {group: categoryTemperature, allowPrefix: false}, "kel": {group: categoryTemperature, allowPrefix: false}, "Rank": {group: categoryTemperature, allowPrefix: false}, "Reau": {group: categoryTemperature, allowPrefix: false}, // volume "l": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "L": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "lt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "tsp": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "tspm": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "tbs": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "oz": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "cup": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "us_pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "uk_pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "qt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "uk_qt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "gal": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "uk_gal": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "ang3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "ang^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "barrel": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "bushel": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "in3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "in^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "ft3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "ft^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "ly3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "ly^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "m3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "m^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true}, "mi3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "mi^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "yd3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "yd^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Nmi3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Nmi^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Pica3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Pica^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Picapt3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "Picapt^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "GRT": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "regton": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, "MTON": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false}, // area "ha": {group: categoryArea, allowPrefix: true}, "uk_acre": {group: categoryArea, allowPrefix: false}, "us_acre": {group: categoryArea, allowPrefix: false}, "ang2": {group: categoryArea, allowPrefix: true}, "ang^2": {group: categoryArea, allowPrefix: true}, "ar": {group: categoryArea, allowPrefix: true}, "ft2": {group: categoryArea, allowPrefix: false}, "ft^2": {group: categoryArea, allowPrefix: false}, "in2": {group: categoryArea, allowPrefix: false}, "in^2": {group: categoryArea, allowPrefix: false}, "ly2": {group: categoryArea, allowPrefix: false}, "ly^2": {group: categoryArea, allowPrefix: false}, "m2": {group: categoryArea, allowPrefix: true}, "m^2": {group: categoryArea, allowPrefix: true}, "Morgen": {group: categoryArea, allowPrefix: false}, "mi2": {group: categoryArea, allowPrefix: false}, "mi^2": {group: categoryArea, allowPrefix: false}, "Nmi2": {group: categoryArea, allowPrefix: false}, "Nmi^2": {group: categoryArea, allowPrefix: false}, "Pica2": {group: categoryArea, allowPrefix: false}, "Pica^2": {group: categoryArea, allowPrefix: false}, "Picapt2": {group: categoryArea, allowPrefix: false}, "Picapt^2": {group: categoryArea, allowPrefix: false}, "yd2": {group: categoryArea, allowPrefix: false}, "yd^2": {group: categoryArea, allowPrefix: false}, // information "byte": {group: categoryInformation, allowPrefix: true}, "bit": {group: categoryInformation, allowPrefix: true}, // speed "m/s": {group: categorySpeed, allowPrefix: true}, "m/sec": {group: categorySpeed, allowPrefix: true}, "m/h": {group: categorySpeed, allowPrefix: true}, "m/hr": {group: categorySpeed, allowPrefix: true}, "mph": {group: categorySpeed, allowPrefix: false}, "admkn": {group: categorySpeed, allowPrefix: false}, "kn": {group: categorySpeed, allowPrefix: false}, } // unitConversions maps details of the Units of measure conversion factors, // organised by group. var unitConversions = map[byte]map[string]float64{ // conversion uses gram (g) as an intermediate unit categoryWeightAndMass: { "g": 1, "sg": 6.85217658567918e-05, "lbm": 2.20462262184878e-03, "u": 6.02214179421676e+23, "ozm": 3.52739619495804e-02, "grain": 1.54323583529414e+01, "cwt": 2.20462262184878e-05, "shweight": 2.20462262184878e-05, "uk_cwt": 1.96841305522212e-05, "lcwt": 1.96841305522212e-05, "hweight": 1.96841305522212e-05, "stone": 1.57473044417770e-04, "ton": 1.10231131092439e-06, "uk_ton": 9.84206527611061e-07, "LTON": 9.84206527611061e-07, "brton": 9.84206527611061e-07, }, // conversion uses meter (m) as an intermediate unit categoryDistance: { "m": 1, "mi": 6.21371192237334e-04, "Nmi": 5.39956803455724e-04, "in": 3.93700787401575e+01, "ft": 3.28083989501312e+00, "yd": 1.09361329833771e+00, "ang": 1.0e+10, "ell": 8.74890638670166e-01, "ly": 1.05700083402462e-16, "parsec": 3.24077928966473e-17, "pc": 3.24077928966473e-17, "Pica": 2.83464566929134e+03, "Picapt": 2.83464566929134e+03, "pica": 2.36220472440945e+02, "survey_mi": 6.21369949494950e-04, }, // conversion uses second (s) as an intermediate unit categoryTime: { "yr": 3.16880878140289e-08, "day": 1.15740740740741e-05, "d": 1.15740740740741e-05, "hr": 2.77777777777778e-04, "mn": 1.66666666666667e-02, "min": 1.66666666666667e-02, "sec": 1, "s": 1, }, // conversion uses Pascal (Pa) as an intermediate unit categoryPressure: { "Pa": 1, "p": 1, "atm": 9.86923266716013e-06, "at": 9.86923266716013e-06, "mmHg": 7.50063755419211e-03, "psi": 1.45037737730209e-04, "Torr": 7.50061682704170e-03, }, // conversion uses Newton (N) as an intermediate unit categoryForce: { "N": 1, "dyn": 1.0e+5, "dy": 1.0e+5, "lbf": 2.24808923655339e-01, "pond": 1.01971621297793e+02, }, // conversion uses Joule (J) as an intermediate unit categoryEnergy: { "J": 1, "e": 9.99999519343231e+06, "c": 2.39006249473467e-01, "cal": 2.38846190642017e-01, "eV": 6.24145700000000e+18, "ev": 6.24145700000000e+18, "HPh": 3.72506430801000e-07, "hh": 3.72506430801000e-07, "Wh": 2.77777916238711e-04, "wh": 2.77777916238711e-04, "flb": 2.37304222192651e+01, "BTU": 9.47815067349015e-04, "btu": 9.47815067349015e-04, }, // conversion uses Horsepower (HP) as an intermediate unit categoryPower: { "HP": 1, "h": 1, "W": 7.45699871582270e+02, "w": 7.45699871582270e+02, "PS": 1.01386966542400e+00, }, // conversion uses Tesla (T) as an intermediate unit categoryMagnetism: { "T": 1, "ga": 10000, }, // conversion uses litre (l) as an intermediate unit categoryVolumeAndLiquidMeasure: { "l": 1, "L": 1, "lt": 1, "tsp": 2.02884136211058e+02, "tspm": 2.0e+02, "tbs": 6.76280454036860e+01, "oz": 3.38140227018430e+01, "cup": 4.22675283773038e+00, "pt": 2.11337641886519e+00, "us_pt": 2.11337641886519e+00, "uk_pt": 1.75975398639270e+00, "qt": 1.05668820943259e+00, "uk_qt": 8.79876993196351e-01, "gal": 2.64172052358148e-01, "uk_gal": 2.19969248299088e-01, "ang3": 1.0e+27, "ang^3": 1.0e+27, "barrel": 6.28981077043211e-03, "bushel": 2.83775932584017e-02, "in3": 6.10237440947323e+01, "in^3": 6.10237440947323e+01, "ft3": 3.53146667214886e-02, "ft^3": 3.53146667214886e-02, "ly3": 1.18093498844171e-51, "ly^3": 1.18093498844171e-51, "m3": 1.0e-03, "m^3": 1.0e-03, "mi3": 2.39912758578928e-13, "mi^3": 2.39912758578928e-13, "yd3": 1.30795061931439e-03, "yd^3": 1.30795061931439e-03, "Nmi3": 1.57426214685811e-13, "Nmi^3": 1.57426214685811e-13, "Pica3": 2.27769904358706e+07, "Pica^3": 2.27769904358706e+07, "Picapt3": 2.27769904358706e+07, "Picapt^3": 2.27769904358706e+07, "GRT": 3.53146667214886e-04, "regton": 3.53146667214886e-04, "MTON": 8.82866668037215e-04, }, // conversion uses hectare (ha) as an intermediate unit categoryArea: { "ha": 1, "uk_acre": 2.47105381467165e+00, "us_acre": 2.47104393046628e+00, "ang2": 1.0e+24, "ang^2": 1.0e+24, "ar": 1.0e+02, "ft2": 1.07639104167097e+05, "ft^2": 1.07639104167097e+05, "in2": 1.55000310000620e+07, "in^2": 1.55000310000620e+07, "ly2": 1.11725076312873e-28, "ly^2": 1.11725076312873e-28, "m2": 1.0e+04, "m^2": 1.0e+04, "Morgen": 4.0e+00, "mi2": 3.86102158542446e-03, "mi^2": 3.86102158542446e-03, "Nmi2": 2.91553349598123e-03, "Nmi^2": 2.91553349598123e-03, "Pica2": 8.03521607043214e+10, "Pica^2": 8.03521607043214e+10, "Picapt2": 8.03521607043214e+10, "Picapt^2": 8.03521607043214e+10, "yd2": 1.19599004630108e+04, "yd^2": 1.19599004630108e+04, }, // conversion uses bit (bit) as an intermediate unit categoryInformation: { "bit": 1, "byte": 0.125, }, // conversion uses Meters per Second (m/s) as an intermediate unit categorySpeed: { "m/s": 1, "m/sec": 1, "m/h": 3.60e+03, "m/hr": 3.60e+03, "mph": 2.23693629205440e+00, "admkn": 1.94260256941567e+00, "kn": 1.94384449244060e+00, }, } // conversionMultipliers maps details of the Multiplier prefixes that can be // used with Units of Measure in CONVERT. var conversionMultipliers = map[string]float64{ "Y": 1e24, "Z": 1e21, "E": 1e18, "P": 1e15, "T": 1e12, "G": 1e9, "M": 1e6, "k": 1e3, "h": 1e2, "e": 1e1, "da": 1e1, "d": 1e-1, "c": 1e-2, "m": 1e-3, "u": 1e-6, "n": 1e-9, "p": 1e-12, "f": 1e-15, "a": 1e-18, "z": 1e-21, "y": 1e-24, "Yi": math.Pow(2, 80), "Zi": math.Pow(2, 70), "Ei": math.Pow(2, 60), "Pi": math.Pow(2, 50), "Ti": math.Pow(2, 40), "Gi": math.Pow(2, 30), "Mi": math.Pow(2, 20), "ki": math.Pow(2, 10), } // getUnitDetails check and returns the unit of measure details. func getUnitDetails(uom string) (unit string, catgory byte, res float64, ok bool) { if len(uom) == 0 { ok = false return } if unit, ok := conversionUnits[uom]; ok { return uom, unit.group, 1, ok } // 1 character standard metric multiplier prefixes multiplierType := uom[:1] uom = uom[1:] conversionUnit, ok1 := conversionUnits[uom] multiplier, ok2 := conversionMultipliers[multiplierType] if ok1 && ok2 { if !conversionUnit.allowPrefix { ok = false return } unitCategory := conversionUnit.group return uom, unitCategory, multiplier, true } // 2 character standard and binary metric multiplier prefixes if len(uom) > 0 { multiplierType += uom[:1] uom = uom[1:] } conversionUnit, ok1 = conversionUnits[uom] multiplier, ok2 = conversionMultipliers[multiplierType] if ok1 && ok2 { if !conversionUnit.allowPrefix { ok = false return } unitCategory := conversionUnit.group return uom, unitCategory, multiplier, true } ok = false return } // resolveTemperatureSynonyms returns unit of measure according to a given // temperature synonyms. func resolveTemperatureSynonyms(uom string) string { switch uom { case "fah": return "F" case "cel": return "C" case "kel": return "K" } return uom } // convertTemperature returns converted temperature by a given unit of measure. func convertTemperature(fromUOM, toUOM string, value float64) float64 { fromUOM = resolveTemperatureSynonyms(fromUOM) toUOM = resolveTemperatureSynonyms(toUOM) if fromUOM == toUOM { return value } // convert to Kelvin switch fromUOM { case "F": value = (value-32)/1.8 + 273.15 case "C": value += 273.15 case "Rank": value /= 1.8 case "Reau": value = value*1.25 + 273.15 } // convert from Kelvin switch toUOM { case "F": value = (value-273.15)*1.8 + 32 case "C": value -= 273.15 case "Rank": value *= 1.8 case "Reau": value = (value - 273.15) * 0.8 } return value } // CONVERT function converts a number from one unit type (e.g. Yards) to // another unit type (e.g. Meters). The syntax of the function is: // // CONVERT(number,from_unit,to_unit) func (fn *formulaFuncs) CONVERT(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "CONVERT requires 3 arguments") } num := argsList.Front().Value.(formulaArg).ToNumber() if num.Type != ArgNumber { return num } fromUOM, fromCategory, fromMultiplier, ok1 := getUnitDetails(argsList.Front().Next().Value.(formulaArg).Value()) toUOM, toCategory, toMultiplier, ok2 := getUnitDetails(argsList.Back().Value.(formulaArg).Value()) if !ok1 || !ok2 || fromCategory != toCategory { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } val := num.Number * fromMultiplier if fromUOM == toUOM && fromMultiplier == toMultiplier { return newNumberFormulaArg(val / fromMultiplier) } else if fromUOM == toUOM { return newNumberFormulaArg(val / toMultiplier) } else if fromCategory == categoryTemperature { return newNumberFormulaArg(convertTemperature(fromUOM, toUOM, val)) } fromConversion := unitConversions[fromCategory][fromUOM] toConversion := unitConversions[fromCategory][toUOM] baseValue := val * (1 / fromConversion) return newNumberFormulaArg((baseValue * toConversion) / toMultiplier) } // DEC2BIN function converts a decimal number into a Binary (Base 2) number. // The syntax of the function is: // // DEC2BIN(number,[places]) func (fn *formulaFuncs) DEC2BIN(argsList *list.List) formulaArg { return fn.dec2x("DEC2BIN", argsList) } // DEC2HEX function converts a decimal number into a Hexadecimal (Base 16) // number. The syntax of the function is: // // DEC2HEX(number,[places]) func (fn *formulaFuncs) DEC2HEX(argsList *list.List) formulaArg { return fn.dec2x("DEC2HEX", argsList) } // DEC2OCT function converts a decimal number into an Octal (Base 8) number. // The syntax of the function is: // // DEC2OCT(number,[places]) func (fn *formulaFuncs) DEC2OCT(argsList *list.List) formulaArg { return fn.dec2x("DEC2OCT", argsList) } // dec2x is an implementation of the formula functions DEC2BIN, DEC2HEX and // DEC2OCT. func (fn *formulaFuncs) dec2x(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) } decimal := argsList.Front().Value.(formulaArg).ToNumber() if decimal.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, decimal.Error) } maxLimitMap := map[string]float64{ "DEC2BIN": 511, "HEX2BIN": 511, "OCT2BIN": 511, "BIN2HEX": 549755813887, "DEC2HEX": 549755813887, "OCT2HEX": 549755813887, "BIN2OCT": 536870911, "DEC2OCT": 536870911, "HEX2OCT": 536870911, } minLimitMap := map[string]float64{ "DEC2BIN": -512, "HEX2BIN": -512, "OCT2BIN": -512, "BIN2HEX": -549755813888, "DEC2HEX": -549755813888, "OCT2HEX": -549755813888, "BIN2OCT": -536870912, "DEC2OCT": -536870912, "HEX2OCT": -536870912, } baseMap := map[string]int{ "DEC2BIN": 2, "HEX2BIN": 2, "OCT2BIN": 2, "BIN2HEX": 16, "DEC2HEX": 16, "OCT2HEX": 16, "BIN2OCT": 8, "DEC2OCT": 8, "HEX2OCT": 8, } maxLimit, minLimit := maxLimitMap[name], minLimitMap[name] base := baseMap[name] if decimal.Number < minLimit || decimal.Number > maxLimit { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } n := int64(decimal.Number) binary := strconv.FormatUint(*(*uint64)(unsafe.Pointer(&n)), base) if argsList.Len() == 2 { places := argsList.Back().Value.(formulaArg).ToNumber() if places.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, places.Error) } binaryPlaces := len(binary) if places.Number < 0 || places.Number > 10 || binaryPlaces > int(places.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%s%s", strings.Repeat("0", int(places.Number)-binaryPlaces), binary))) } if decimal.Number < 0 && len(binary) > 10 { return newStringFormulaArg(strings.ToUpper(binary[len(binary)-10:])) } return newStringFormulaArg(strings.ToUpper(binary)) } // DELTA function tests two numbers for equality and returns the Kronecker // Delta. i.e. the function returns 1 if the two supplied numbers are equal // and 0 otherwise. The syntax of the function is: // // DELTA(number1,[number2]) func (fn *formulaFuncs) DELTA(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "DELTA requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "DELTA allows at most 2 arguments") } number1 := argsList.Front().Value.(formulaArg).ToNumber() if number1.Type != ArgNumber { return number1 } number2 := newNumberFormulaArg(0) if argsList.Len() == 2 { if number2 = argsList.Back().Value.(formulaArg).ToNumber(); number2.Type != ArgNumber { return number2 } } return newBoolFormulaArg(number1.Number == number2.Number).ToNumber() } // ERF function calculates the Error Function, integrated between two supplied // limits. The syntax of the function is: // // ERF(lower_limit,[upper_limit]) func (fn *formulaFuncs) ERF(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "ERF requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "ERF allows at most 2 arguments") } lower := argsList.Front().Value.(formulaArg).ToNumber() if lower.Type != ArgNumber { return lower } if argsList.Len() == 2 { upper := argsList.Back().Value.(formulaArg).ToNumber() if upper.Type != ArgNumber { return upper } return newNumberFormulaArg(math.Erf(upper.Number) - math.Erf(lower.Number)) } return newNumberFormulaArg(math.Erf(lower.Number)) } // ERFdotPRECISE function calculates the Error Function, integrated between a // supplied lower or upper limit and 0. The syntax of the function is: // // ERF.PRECISE(x) func (fn *formulaFuncs) ERFdotPRECISE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ERF.PRECISE requires 1 argument") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } return newNumberFormulaArg(math.Erf(x.Number)) } // erfc is an implementation of the formula functions ERFC and ERFC.PRECISE. func (fn *formulaFuncs) erfc(name string, argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name)) } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } return newNumberFormulaArg(math.Erfc(x.Number)) } // ERFC function calculates the Complementary Error Function, integrated // between a supplied lower limit and infinity. The syntax of the function // is: // // ERFC(x) func (fn *formulaFuncs) ERFC(argsList *list.List) formulaArg { return fn.erfc("ERFC", argsList) } // ERFCdotPRECISE function calculates the Complementary Error Function, // integrated between a supplied lower limit and infinity. The syntax of the // function is: // // ERFC(x) func (fn *formulaFuncs) ERFCdotPRECISE(argsList *list.List) formulaArg { return fn.erfc("ERFC.PRECISE", argsList) } // GESTEP unction tests whether a supplied number is greater than a supplied // step size and returns. The syntax of the function is: // // GESTEP(number,[step]) func (fn *formulaFuncs) GESTEP(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "GESTEP requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "GESTEP allows at most 2 arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type != ArgNumber { return number } step := newNumberFormulaArg(0) if argsList.Len() == 2 { if step = argsList.Back().Value.(formulaArg).ToNumber(); step.Type != ArgNumber { return step } } return newBoolFormulaArg(number.Number >= step.Number).ToNumber() } // HEX2BIN function converts a Hexadecimal (Base 16) number into a Binary // (Base 2) number. The syntax of the function is: // // HEX2BIN(number,[places]) func (fn *formulaFuncs) HEX2BIN(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN allows at most 2 arguments") } decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New() if decimal.Type != ArgNumber { return decimal } newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("HEX2BIN", newList) } // HEX2DEC function converts a hexadecimal (a base-16 number) into a decimal // number. The syntax of the function is: // // HEX2DEC(number) func (fn *formulaFuncs) HEX2DEC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "HEX2DEC requires 1 numeric argument") } return fn.hex2dec(argsList.Front().Value.(formulaArg).Value()) } // HEX2OCT function converts a Hexadecimal (Base 16) number into an Octal // (Base 8) number. The syntax of the function is: // // HEX2OCT(number,[places]) func (fn *formulaFuncs) HEX2OCT(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT allows at most 2 arguments") } decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New() if decimal.Type != ArgNumber { return decimal } newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("HEX2OCT", newList) } // hex2dec is an implementation of the formula function HEX2DEC. func (fn *formulaFuncs) hex2dec(number string) formulaArg { decimal, length := 0.0, len(number) for i := length; i > 0; i-- { num, err := strconv.ParseInt(string(number[length-i]), 16, 64) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } if i == 10 && string(number[length-i]) == "F" { decimal += math.Pow(-16.0, float64(i-1)) continue } decimal += float64(num) * math.Pow(16.0, float64(i-1)) } return newNumberFormulaArg(decimal) } // IMABS function returns the absolute value (the modulus) of a complex // number. The syntax of the function is: // // IMABS(inumber) func (fn *formulaFuncs) IMABS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMABS requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newNumberFormulaArg(cmplx.Abs(inumber)) } // IMAGINARY function returns the imaginary coefficient of a supplied complex // number. The syntax of the function is: // // IMAGINARY(inumber) func (fn *formulaFuncs) IMAGINARY(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMAGINARY requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newNumberFormulaArg(imag(inumber)) } // IMARGUMENT function returns the phase (also called the argument) of a // supplied complex number. The syntax of the function is: // // IMARGUMENT(inumber) func (fn *formulaFuncs) IMARGUMENT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMARGUMENT requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newNumberFormulaArg(cmplx.Phase(inumber)) } // IMCONJUGATE function returns the complex conjugate of a supplied complex // number. The syntax of the function is: // // IMCONJUGATE(inumber) func (fn *formulaFuncs) IMCONJUGATE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCONJUGATE requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Conj(inumber), value[len(value)-1:])) } // IMCOS function returns the cosine of a supplied complex number. The syntax // of the function is: // // IMCOS(inumber) func (fn *formulaFuncs) IMCOS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCOS requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Cos(inumber), value[len(value)-1:])) } // IMCOSH function returns the hyperbolic cosine of a supplied complex number. The syntax // of the function is: // // IMCOSH(inumber) func (fn *formulaFuncs) IMCOSH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCOSH requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Cosh(inumber), value[len(value)-1:])) } // IMCOT function returns the cotangent of a supplied complex number. The syntax // of the function is: // // IMCOT(inumber) func (fn *formulaFuncs) IMCOT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCOT requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Cot(inumber), value[len(value)-1:])) } // IMCSC function returns the cosecant of a supplied complex number. The syntax // of the function is: // // IMCSC(inumber) func (fn *formulaFuncs) IMCSC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCSC requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := 1 / cmplx.Sin(inumber) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMCSCH function returns the hyperbolic cosecant of a supplied complex // number. The syntax of the function is: // // IMCSCH(inumber) func (fn *formulaFuncs) IMCSCH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMCSCH requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := 1 / cmplx.Sinh(inumber) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMDIV function calculates the quotient of two complex numbers (i.e. divides // one complex number by another). The syntax of the function is: // // IMDIV(inumber1,inumber2) func (fn *formulaFuncs) IMDIV(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "IMDIV requires 2 arguments") } value := argsList.Front().Value.(formulaArg).Value() inumber1, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } inumber2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := inumber1 / inumber2 if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMEXP function returns the exponential of a supplied complex number. The // syntax of the function is: // // IMEXP(inumber) func (fn *formulaFuncs) IMEXP(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMEXP requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Exp(inumber), value[len(value)-1:])) } // IMLN function returns the natural logarithm of a supplied complex number. // The syntax of the function is: // // IMLN(inumber) func (fn *formulaFuncs) IMLN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMLN requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := cmplx.Log(inumber) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMLOG10 function returns the common (base 10) logarithm of a supplied // complex number. The syntax of the function is: // // IMLOG10(inumber) func (fn *formulaFuncs) IMLOG10(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMLOG10 requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := cmplx.Log10(inumber) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMLOG2 function calculates the base 2 logarithm of a supplied complex // number. The syntax of the function is: // // IMLOG2(inumber) func (fn *formulaFuncs) IMLOG2(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMLOG2 requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } num := cmplx.Log(inumber) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num/cmplx.Log(2), value[len(value)-1:])) } // IMPOWER function returns a supplied complex number, raised to a given // power. The syntax of the function is: // // IMPOWER(inumber,number) func (fn *formulaFuncs) IMPOWER(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "IMPOWER requires 2 arguments") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } number, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } if inumber == 0 && number == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } num := cmplx.Pow(inumber, number) if cmplx.IsInf(num) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) } // IMPRODUCT function calculates the product of two or more complex numbers. // The syntax of the function is: // // IMPRODUCT(number1,[number2],...) func (fn *formulaFuncs) IMPRODUCT(argsList *list.List) formulaArg { product := complex128(1) for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: if token.Value() == "" { continue } val, err := strconv.ParseComplex(str2cmplx(token.Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } product = product * val case ArgNumber: product = product * complex(token.Number, 0) case ArgMatrix: for _, row := range token.Matrix { for _, value := range row { if value.Value() == "" { continue } val, err := strconv.ParseComplex(str2cmplx(value.Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } product = product * val } } } } return newStringFormulaArg(cmplx2str(product, "i")) } // IMREAL function returns the real coefficient of a supplied complex number. // The syntax of the function is: // // IMREAL(inumber) func (fn *formulaFuncs) IMREAL(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMREAL requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(fmt.Sprint(real(inumber))) } // IMSEC function returns the secant of a supplied complex number. The syntax // of the function is: // // IMSEC(inumber) func (fn *formulaFuncs) IMSEC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSEC requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(1/cmplx.Cos(inumber), value[len(value)-1:])) } // IMSECH function returns the hyperbolic secant of a supplied complex number. // The syntax of the function is: // // IMSECH(inumber) func (fn *formulaFuncs) IMSECH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSECH requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(1/cmplx.Cosh(inumber), value[len(value)-1:])) } // IMSIN function returns the Sine of a supplied complex number. The syntax of // the function is: // // IMSIN(inumber) func (fn *formulaFuncs) IMSIN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSIN requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Sin(inumber), value[len(value)-1:])) } // IMSINH function returns the hyperbolic sine of a supplied complex number. // The syntax of the function is: // // IMSINH(inumber) func (fn *formulaFuncs) IMSINH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSINH requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Sinh(inumber), value[len(value)-1:])) } // IMSQRT function returns the square root of a supplied complex number. The // syntax of the function is: // // IMSQRT(inumber) func (fn *formulaFuncs) IMSQRT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSQRT requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Sqrt(inumber), value[len(value)-1:])) } // IMSUB function calculates the difference between two complex numbers // (i.e. subtracts one complex number from another). The syntax of the // function is: // // IMSUB(inumber1,inumber2) func (fn *formulaFuncs) IMSUB(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "IMSUB requires 2 arguments") } i1, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } i2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(i1-i2, "i")) } // IMSUM function calculates the sum of two or more complex numbers. The // syntax of the function is: // // IMSUM(inumber1,inumber2,...) func (fn *formulaFuncs) IMSUM(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMSUM requires at least 1 argument") } var result complex128 for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) num, err := strconv.ParseComplex(str2cmplx(token.Value()), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } result += num } return newStringFormulaArg(cmplx2str(result, "i")) } // IMTAN function returns the tangent of a supplied complex number. The syntax // of the function is: // // IMTAN(inumber) func (fn *formulaFuncs) IMTAN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "IMTAN requires 1 argument") } value := argsList.Front().Value.(formulaArg).Value() inumber, err := strconv.ParseComplex(str2cmplx(value), 128) if err != nil { return newErrorFormulaArg(formulaErrorNUM, err.Error()) } return newStringFormulaArg(cmplx2str(cmplx.Tan(inumber), value[len(value)-1:])) } // OCT2BIN function converts an Octal (Base 8) number into a Binary (Base 2) // number. The syntax of the function is: // // OCT2BIN(number,[places]) func (fn *formulaFuncs) OCT2BIN(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN allows at most 2 arguments") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } decimal, newList := fn.oct2dec(token.Value()), list.New() newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("OCT2BIN", newList) } // OCT2DEC function converts an Octal (a base-8 number) into a decimal number. // The syntax of the function is: // // OCT2DEC(number) func (fn *formulaFuncs) OCT2DEC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "OCT2DEC requires 1 numeric argument") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } return fn.oct2dec(token.Value()) } // OCT2HEX function converts an Octal (Base 8) number into a Hexadecimal // (Base 16) number. The syntax of the function is: // // OCT2HEX(number,[places]) func (fn *formulaFuncs) OCT2HEX(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX allows at most 2 arguments") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } decimal, newList := fn.oct2dec(token.Value()), list.New() newList.PushBack(decimal) if argsList.Len() == 2 { newList.PushBack(argsList.Back().Value.(formulaArg)) } return fn.dec2x("OCT2HEX", newList) } // oct2dec is an implementation of the formula function OCT2DEC. func (fn *formulaFuncs) oct2dec(number string) formulaArg { decimal, length := 0.0, len(number) for i := length; i > 0; i-- { num, _ := strconv.Atoi(string(number[length-i])) if i == 10 && string(number[length-i]) == "7" { decimal += math.Pow(-8.0, float64(i-1)) continue } decimal += float64(num) * math.Pow(8.0, float64(i-1)) } return newNumberFormulaArg(decimal) } // Math and Trigonometric Functions // ABS function returns the absolute value of any supplied number. The syntax // of the function is: // // ABS(number) func (fn *formulaFuncs) ABS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ABS requires 1 numeric argument") } arg := fn.implicitIntersect(argsList.Front().Value.(formulaArg)).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Abs(arg.Number)) } // ACOS function calculates the arccosine (i.e. the inverse cosine) of a given // number, and returns an angle, in radians, between 0 and π. The syntax of // the function is: // // ACOS(number) func (fn *formulaFuncs) ACOS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ACOS requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Acos(arg.Number)) } // ACOSH function calculates the inverse hyperbolic cosine of a supplied number. // of the function is: // // ACOSH(number) func (fn *formulaFuncs) ACOSH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ACOSH requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Acosh(arg.Number)) } // ACOT function calculates the arccotangent (i.e. the inverse cotangent) of a // given number, and returns an angle, in radians, between 0 and π. The syntax // of the function is: // // ACOT(number) func (fn *formulaFuncs) ACOT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ACOT requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Pi/2 - math.Atan(arg.Number)) } // ACOTH function calculates the hyperbolic arccotangent (coth) of a supplied // value. The syntax of the function is: // // ACOTH(number) func (fn *formulaFuncs) ACOTH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ACOTH requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Atanh(1 / arg.Number)) } // AGGREGATE function returns the result of a specified operation or function, // applied to a list or database of values. The syntax of the function is: // // AGGREGATE(function_num,options,ref1,[ref2],...) func (fn *formulaFuncs) AGGREGATE(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE requires at least 3 arguments") } var fnNum, opts formulaArg if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber { return fnNum } subFn, ok := map[int]func(argsList *list.List) formulaArg{ 1: fn.AVERAGE, 2: fn.COUNT, 3: fn.COUNTA, 4: fn.MAX, 5: fn.MIN, 6: fn.PRODUCT, 7: fn.STDEVdotS, 8: fn.STDEVdotP, 9: fn.SUM, 10: fn.VARdotS, 11: fn.VARdotP, 12: fn.MEDIAN, 13: fn.MODEdotSNGL, 14: fn.LARGE, 15: fn.SMALL, 16: fn.PERCENTILEdotINC, 17: fn.QUARTILEdotINC, 18: fn.PERCENTILEdotEXC, 19: fn.QUARTILEdotEXC, }[int(fnNum.Number)] if !ok { return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid function_num") } if opts = argsList.Front().Next().Value.(formulaArg).ToNumber(); opts.Type != ArgNumber { return opts } // TODO: apply option argument values to be ignored during the calculation if int(opts.Number) < 0 || int(opts.Number) > 7 { return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid options") } subArgList := list.New().Init() for arg := argsList.Front().Next().Next(); arg != nil; arg = arg.Next() { subArgList.PushBack(arg.Value.(formulaArg)) } return subFn(subArgList) } // ARABIC function converts a Roman numeral into an Arabic numeral. The syntax // of the function is: // // ARABIC(text) func (fn *formulaFuncs) ARABIC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument") } text := argsList.Front().Value.(formulaArg).Value() if countUTF16String(text) > MaxFieldLength { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } text = strings.ToUpper(text) number, actualStart, index, isNegative := 0, 0, len(text)-1, false startIndex, subtractNumber, currentPartValue, currentCharValue, prevCharValue := 0, 0, 0, 0, -1 for index >= 0 && text[index] == ' ' { index-- } for actualStart <= index && text[actualStart] == ' ' { actualStart++ } if actualStart <= index && text[actualStart] == '-' { isNegative = true actualStart++ } charMap := map[rune]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000} for index >= actualStart { startIndex = index startChar := text[startIndex] index-- for index >= actualStart && (text[index]|' ') == startChar { index-- } currentCharValue = charMap[rune(startChar)] currentPartValue = (startIndex - index) * currentCharValue if currentCharValue >= prevCharValue { number += currentPartValue - subtractNumber prevCharValue = currentCharValue subtractNumber = 0 continue } subtractNumber += currentPartValue } if subtractNumber != 0 { number -= subtractNumber } if isNegative { number = -number } return newNumberFormulaArg(float64(number)) } // ASIN function calculates the arcsine (i.e. the inverse sine) of a given // number, and returns an angle, in radians, between -π/2 and π/2. The syntax // of the function is: // // ASIN(number) func (fn *formulaFuncs) ASIN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ASIN requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Asin(arg.Number)) } // ASINH function calculates the inverse hyperbolic sine of a supplied number. // The syntax of the function is: // // ASINH(number) func (fn *formulaFuncs) ASINH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ASINH requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Asinh(arg.Number)) } // ATAN function calculates the arctangent (i.e. the inverse tangent) of a // given number, and returns an angle, in radians, between -π/2 and +π/2. The // syntax of the function is: // // ATAN(number) func (fn *formulaFuncs) ATAN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ATAN requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Atan(arg.Number)) } // ATANH function calculates the inverse hyperbolic tangent of a supplied // number. The syntax of the function is: // // ATANH(number) func (fn *formulaFuncs) ATANH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ATANH requires 1 numeric argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type == ArgError { return arg } return newNumberFormulaArg(math.Atanh(arg.Number)) } // ATAN2 function calculates the arctangent (i.e. the inverse tangent) of a // given set of x and y coordinates, and returns an angle, in radians, between // -π/2 and +π/2. The syntax of the function is: // // ATAN2(x_num,y_num) func (fn *formulaFuncs) ATAN2(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "ATAN2 requires 2 numeric arguments") } x := argsList.Back().Value.(formulaArg).ToNumber() if x.Type == ArgError { return x } y := argsList.Front().Value.(formulaArg).ToNumber() if y.Type == ArgError { return y } return newNumberFormulaArg(math.Atan2(x.Number, y.Number)) } // BASE function converts a number into a supplied base (radix), and returns a // text representation of the calculated value. The syntax of the function is: // // BASE(number,radix,[min_length]) func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "BASE requires at least 2 arguments") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "BASE allows at most 3 arguments") } var minLength int var err error number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } radix := argsList.Front().Next().Value.(formulaArg).ToNumber() if radix.Type == ArgError { return radix } if int(radix.Number) < 2 || int(radix.Number) > 36 { return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36") } if argsList.Len() > 2 { if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).Value()); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } } result := strconv.FormatInt(int64(number.Number), int(radix.Number)) if len(result) < minLength { result = strings.Repeat("0", minLength-len(result)) + result } return newStringFormulaArg(strings.ToUpper(result)) } // CEILING function rounds a supplied number away from zero, to the nearest // multiple of a given number. The syntax of the function is: // // CEILING(number,significance) func (fn *formulaFuncs) CEILING(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING allows at most 2 arguments") } number, significance, res := 0.0, 1.0, 0.0 n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } number = n.Number if number < 0 { significance = -1 } if argsList.Len() > 1 { s := argsList.Back().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number } if significance < 0 && number > 0 { return newErrorFormulaArg(formulaErrorVALUE, "negative sig to CEILING invalid") } if argsList.Len() == 1 { return newNumberFormulaArg(math.Ceil(number)) } number, res = math.Modf(number / significance) if res > 0 { number++ } return newNumberFormulaArg(number * significance) } // CEILINGdotMATH function rounds a supplied number up to a supplied multiple // of significance. The syntax of the function is: // // CEILING.MATH(number,[significance],[mode]) func (fn *formulaFuncs) CEILINGdotMATH(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH requires at least 1 argument") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH allows at most 3 arguments") } number, significance, mode := 0.0, 1.0, 1.0 n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } number = n.Number if number < 0 { significance = -1 } if argsList.Len() > 1 { s := argsList.Front().Next().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number } if argsList.Len() == 1 { return newNumberFormulaArg(math.Ceil(number)) } if argsList.Len() > 2 { m := argsList.Back().Value.(formulaArg).ToNumber() if m.Type == ArgError { return m } mode = m.Number } val, res := math.Modf(number / significance) if res != 0 { if number > 0 { val++ } else if mode < 0 { val-- } } return newNumberFormulaArg(val * significance) } // CEILINGdotPRECISE function rounds a supplied number up (regardless of the // number's sign), to the nearest multiple of a given number. The syntax of // the function is: // // CEILING.PRECISE(number,[significance]) func (fn *formulaFuncs) CEILINGdotPRECISE(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE allows at most 2 arguments") } number, significance := 0.0, 1.0 n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } number = n.Number if number < 0 { significance = -1 } if argsList.Len() == 1 { return newNumberFormulaArg(math.Ceil(number)) } if argsList.Len() > 1 { s := argsList.Back().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number significance = math.Abs(significance) if significance == 0 { return newNumberFormulaArg(significance) } } val, res := math.Modf(number / significance) if res != 0 { if number > 0 { val++ } } return newNumberFormulaArg(val * significance) } // COMBIN function calculates the number of combinations (in any order) of a // given number objects from a set. The syntax of the function is: // // COMBIN(number,number_chosen) func (fn *formulaFuncs) COMBIN(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires 2 argument") } number, chosen, val := 0.0, 0.0, 1.0 n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } number = n.Number c := argsList.Back().Value.(formulaArg).ToNumber() if c.Type == ArgError { return c } chosen = c.Number number, chosen = math.Trunc(number), math.Trunc(chosen) if chosen > number { return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires number >= number_chosen") } if chosen == number || chosen == 0 { return newNumberFormulaArg(1) } for c := float64(1); c <= chosen; c++ { val *= (number + 1 - c) / c } return newNumberFormulaArg(math.Ceil(val)) } // COMBINA function calculates the number of combinations, with repetitions, // of a given number objects from a set. The syntax of the function is: // // COMBINA(number,number_chosen) func (fn *formulaFuncs) COMBINA(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires 2 argument") } var number, chosen float64 n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } number = n.Number c := argsList.Back().Value.(formulaArg).ToNumber() if c.Type == ArgError { return c } chosen = c.Number number, chosen = math.Trunc(number), math.Trunc(chosen) if number < chosen { return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires number > number_chosen") } if number == 0 { return newNumberFormulaArg(number) } args := list.New() args.PushBack(formulaArg{ String: fmt.Sprintf("%g", number+chosen-1), Type: ArgString, }) args.PushBack(formulaArg{ String: fmt.Sprintf("%g", number-1), Type: ArgString, }) return fn.COMBIN(args) } // COS function calculates the cosine of a given angle. The syntax of the // function is: // // COS(number) func (fn *formulaFuncs) COS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COS requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } return newNumberFormulaArg(math.Cos(val.Number)) } // COSH function calculates the hyperbolic cosine (cosh) of a supplied number. // The syntax of the function is: // // COSH(number) func (fn *formulaFuncs) COSH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COSH requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } return newNumberFormulaArg(math.Cosh(val.Number)) } // COT function calculates the cotangent of a given angle. The syntax of the // function is: // // COT(number) func (fn *formulaFuncs) COT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COT requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(1 / math.Tan(val.Number)) } // COTH function calculates the hyperbolic cotangent (coth) of a supplied // angle. The syntax of the function is: // // COTH(number) func (fn *formulaFuncs) COTH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COTH requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg((math.Exp(val.Number) + math.Exp(-val.Number)) / (math.Exp(val.Number) - math.Exp(-val.Number))) } // CSC function calculates the cosecant of a given angle. The syntax of the // function is: // // CSC(number) func (fn *formulaFuncs) CSC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "CSC requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(1 / math.Sin(val.Number)) } // CSCH function calculates the hyperbolic cosecant (csch) of a supplied // angle. The syntax of the function is: // // CSCH(number) func (fn *formulaFuncs) CSCH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "CSCH requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(1 / math.Sinh(val.Number)) } // DECIMAL function converts a text representation of a number in a specified // base, into a decimal value. The syntax of the function is: // // DECIMAL(text,radix) func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments") } text := argsList.Front().Value.(formulaArg).Value() var err error radix := argsList.Back().Value.(formulaArg).ToNumber() if radix.Type != ArgNumber { return radix } if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) { text = text[2:] } val, err := strconv.ParseInt(text, int(radix.Number), 64) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } return newNumberFormulaArg(float64(val)) } // DEGREES function converts radians into degrees. The syntax of the function // is: // // DEGREES(angle) func (fn *formulaFuncs) DEGREES(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "DEGREES requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(180.0 / math.Pi * val.Number) } // EVEN function rounds a supplied number away from zero (i.e. rounds a // positive number up and a negative number down), to the next even number. // The syntax of the function is: // // EVEN(number) func (fn *formulaFuncs) EVEN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "EVEN requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } sign := math.Signbit(number.Number) m, frac := math.Modf(number.Number / 2) val := m * 2 if frac != 0 { if !sign { val += 2 } else { val -= 2 } } return newNumberFormulaArg(val) } // EXP function calculates the value of the mathematical constant e, raised to // the power of a given number. The syntax of the function is: // // EXP(number) func (fn *formulaFuncs) EXP(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "EXP requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Exp(number.Number)) } // fact returns the factorial of a supplied number. func fact(number float64) float64 { val := float64(1) for i := float64(2); i <= number; i++ { val *= i } return val } // FACT function returns the factorial of a supplied number. The syntax of the // function is: // // FACT(number) func (fn *formulaFuncs) FACT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "FACT requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(fact(number.Number)) } // FACTDOUBLE function returns the double factorial of a supplied number. The // syntax of the function is: // // FACTDOUBLE(number) func (fn *formulaFuncs) FACTDOUBLE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "FACTDOUBLE requires 1 numeric argument") } val := 1.0 number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } for i := math.Trunc(number.Number); i > 1; i -= 2 { val *= i } return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val))) } // FLOOR function rounds a supplied number towards zero to the nearest // multiple of a specified significance. The syntax of the function is: // // FLOOR(number,significance) func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "FLOOR requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } significance := argsList.Back().Value.(formulaArg).ToNumber() if significance.Type == ArgError { return significance } if significance.Number < 0 && number.Number >= 0 { return newErrorFormulaArg(formulaErrorNUM, "invalid arguments to FLOOR") } val := number.Number val, res := math.Modf(val / significance.Number) if res != 0 { if number.Number < 0 && res < 0 { val-- } } return newNumberFormulaArg(val * significance.Number) } // FLOORdotMATH function rounds a supplied number down to a supplied multiple // of significance. The syntax of the function is: // // FLOOR.MATH(number,[significance],[mode]) func (fn *formulaFuncs) FLOORdotMATH(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH requires at least 1 argument") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH allows at most 3 arguments") } significance, mode := 1.0, 1.0 number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number < 0 { significance = -1 } if argsList.Len() > 1 { s := argsList.Front().Next().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number } if argsList.Len() == 1 { return newNumberFormulaArg(math.Floor(number.Number)) } if argsList.Len() > 2 { m := argsList.Back().Value.(formulaArg).ToNumber() if m.Type == ArgError { return m } mode = m.Number } val, res := math.Modf(number.Number / significance) if res != 0 && number.Number < 0 && mode > 0 { val-- } return newNumberFormulaArg(val * significance) } // FLOORdotPRECISE function rounds a supplied number down to a supplied // multiple of significance. The syntax of the function is: // // FLOOR.PRECISE(number,[significance]) func (fn *formulaFuncs) FLOORdotPRECISE(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE allows at most 2 arguments") } var significance float64 number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number < 0 { significance = -1 } if argsList.Len() == 1 { return newNumberFormulaArg(math.Floor(number.Number)) } if argsList.Len() > 1 { s := argsList.Back().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number significance = math.Abs(significance) if significance == 0 { return newNumberFormulaArg(significance) } } val, res := math.Modf(number.Number / significance) if res != 0 { if number.Number < 0 { val-- } } return newNumberFormulaArg(val * significance) } // gcd returns the greatest common divisor of two supplied integers. func gcd(x, y float64) float64 { x, y = math.Trunc(x), math.Trunc(y) if x == 0 { return y } if y == 0 { return x } for x != y { if x > y { x = x - y } else { y = y - x } } return x } // GCD function returns the greatest common divisor of two or more supplied // integers. The syntax of the function is: // // GCD(number1,[number2],...) func (fn *formulaFuncs) GCD(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "GCD requires at least 1 argument") } var ( val float64 nums []float64 ) for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: num := token.ToNumber() if num.Type == ArgError { return num } val = num.Number case ArgNumber: val = token.Number } nums = append(nums, val) } if nums[0] < 0 { return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments") } if len(nums) == 1 { return newNumberFormulaArg(nums[0]) } cd := nums[0] for i := 1; i < len(nums); i++ { if nums[i] < 0 { return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments") } cd = gcd(cd, nums[i]) } return newNumberFormulaArg(cd) } // INT function truncates a supplied number down to the closest integer. The // syntax of the function is: // // INT(number) func (fn *formulaFuncs) INT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "INT requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } val, frac := math.Modf(number.Number) if frac < 0 { val-- } return newNumberFormulaArg(val) } // ISOdotCEILING function rounds a supplied number up (regardless of the // number's sign), to the nearest multiple of a supplied significance. The // syntax of the function is: // // ISO.CEILING(number,[significance]) func (fn *formulaFuncs) ISOdotCEILING(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING allows at most 2 arguments") } var significance float64 number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number < 0 { significance = -1 } if argsList.Len() == 1 { return newNumberFormulaArg(math.Ceil(number.Number)) } if argsList.Len() > 1 { s := argsList.Back().Value.(formulaArg).ToNumber() if s.Type == ArgError { return s } significance = s.Number significance = math.Abs(significance) if significance == 0 { return newNumberFormulaArg(significance) } } val, res := math.Modf(number.Number / significance) if res != 0 { if number.Number > 0 { val++ } } return newNumberFormulaArg(val * significance) } // lcm returns the least common multiple of two supplied integers. func lcm(a, b float64) float64 { a = math.Trunc(a) b = math.Trunc(b) if a == 0 && b == 0 { return 0 } return a * b / gcd(a, b) } // LCM function returns the least common multiple of two or more supplied // integers. The syntax of the function is: // // LCM(number1,[number2],...) func (fn *formulaFuncs) LCM(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "LCM requires at least 1 argument") } var ( val float64 nums []float64 err error ) for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: if token.String == "" { continue } if val, err = strconv.ParseFloat(token.String, 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } case ArgNumber: val = token.Number } nums = append(nums, val) } if nums[0] < 0 { return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments") } if len(nums) == 1 { return newNumberFormulaArg(nums[0]) } cm := nums[0] for i := 1; i < len(nums); i++ { if nums[i] < 0 { return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments") } cm = lcm(cm, nums[i]) } return newNumberFormulaArg(cm) } // LN function calculates the natural logarithm of a given number. The syntax // of the function is: // // LN(number) func (fn *formulaFuncs) LN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "LN requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Log(number.Number)) } // LOG function calculates the logarithm of a given number, to a supplied // base. The syntax of the function is: // // LOG(number,[base]) func (fn *formulaFuncs) LOG(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "LOG requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "LOG allows at most 2 arguments") } base := 10.0 number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if argsList.Len() > 1 { b := argsList.Back().Value.(formulaArg).ToNumber() if b.Type == ArgError { return b } base = b.Number } if number.Number == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV) } if base == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV) } if base == 1 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(math.Log(number.Number) / math.Log(base)) } // LOG10 function calculates the base 10 logarithm of a given number. The // syntax of the function is: // // LOG10(number) func (fn *formulaFuncs) LOG10(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "LOG10 requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Log10(number.Number)) } // minor function implement a minor of a matrix A is the determinant of some // smaller square matrix. func minor(sqMtx [][]float64, idx int) [][]float64 { var ret [][]float64 for i := range sqMtx { if i == 0 { continue } var row []float64 for j := range sqMtx { if j == idx { continue } row = append(row, sqMtx[i][j]) } ret = append(ret, row) } return ret } // det determinant of the 2x2 matrix. func det(sqMtx [][]float64) float64 { if len(sqMtx) == 2 { m00 := sqMtx[0][0] m01 := sqMtx[0][1] m10 := sqMtx[1][0] m11 := sqMtx[1][1] return m00*m11 - m10*m01 } var res, sgn float64 = 0, 1 for j := range sqMtx { res += sgn * sqMtx[0][j] * det(minor(sqMtx, j)) sgn *= -1 } return res } // newNumberMatrix converts a formula arguments matrix to a number matrix. func newNumberMatrix(arg formulaArg, phalanx bool) (numMtx [][]float64, ele formulaArg) { rows := len(arg.Matrix) for r, row := range arg.Matrix { if phalanx && len(row) != rows { ele = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return } numMtx = append(numMtx, make([]float64, len(row))) for c, cell := range row { if cell.Type != ArgNumber { ele = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return } numMtx[r][c] = cell.Number } } return } // newFormulaArgMatrix converts the number formula arguments matrix to a // formula arguments matrix. func newFormulaArgMatrix(numMtx [][]float64) (arg [][]formulaArg) { for r, row := range numMtx { arg = append(arg, make([]formulaArg, len(row))) for c, cell := range row { arg[r][c] = newNumberFormulaArg(cell) } } return } // MDETERM calculates the determinant of a square matrix. The // syntax of the function is: // // MDETERM(array) func (fn *formulaFuncs) MDETERM(argsList *list.List) (result formulaArg) { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "MDETERM requires 1 argument") } numMtx, errArg := newNumberMatrix(argsList.Front().Value.(formulaArg), true) if errArg.Type == ArgError { return errArg } return newNumberFormulaArg(det(numMtx)) } // cofactorMatrix returns the matrix A of cofactors. func cofactorMatrix(i, j int, A [][]float64) float64 { N, sign := len(A), -1.0 if (i+j)%2 == 0 { sign = 1 } var B [][]float64 B = append(B, A...) for m := 0; m < N; m++ { for n := j + 1; n < N; n++ { B[m][n-1] = B[m][n] } B[m] = B[m][:len(B[m])-1] } for k := i + 1; k < N; k++ { B[k-1] = B[k] } B = B[:len(B)-1] return sign * det(B) } // adjugateMatrix returns transpose of the cofactor matrix A with Cramer's // rule. func adjugateMatrix(A [][]float64) (adjA [][]float64) { N := len(A) var B [][]float64 for i := 0; i < N; i++ { adjA = append(adjA, make([]float64, N)) for j := 0; j < N; j++ { for m := 0; m < N; m++ { for n := 0; n < N; n++ { for x := len(B); x <= m; x++ { B = append(B, []float64{}) } for k := len(B[m]); k <= n; k++ { B[m] = append(B[m], 0) } B[m][n] = A[m][n] } } adjA[i][j] = cofactorMatrix(j, i, B) } } return } // MINVERSE function calculates the inverse of a square matrix. The syntax of // the function is: // // MINVERSE(array) func (fn *formulaFuncs) MINVERSE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "MINVERSE requires 1 argument") } numMtx, errArg := newNumberMatrix(argsList.Front().Value.(formulaArg), true) if errArg.Type == ArgError { return errArg } if detM := det(numMtx); detM != 0 { datM, invertM := 1/detM, adjugateMatrix(numMtx) for i := 0; i < len(invertM); i++ { for j := 0; j < len(invertM[i]); j++ { invertM[i][j] *= datM } } return newMatrixFormulaArg(newFormulaArgMatrix(invertM)) } return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } // MMULT function calculates the matrix product of two arrays // (representing matrices). The syntax of the function is: // // MMULT(array1,array2) func (fn *formulaFuncs) MMULT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "MMULT requires 2 argument") } arr1 := argsList.Front().Value.(formulaArg) arr2 := argsList.Back().Value.(formulaArg) if arr1.Type == ArgNumber && arr2.Type == ArgNumber { return newNumberFormulaArg(arr1.Number * arr2.Number) } numMtx1, errArg1 := newNumberMatrix(arr1, false) if errArg1.Type == ArgError { return errArg1 } numMtx2, errArg2 := newNumberMatrix(arr2, false) if errArg2.Type == ArgError { return errArg2 } array2Rows, array2Cols := len(numMtx2), len(numMtx2[0]) if len(numMtx1[0]) != array2Rows { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } var numMtx [][]float64 var row1, row []float64 var sum float64 for i := 0; i < len(numMtx1); i++ { numMtx = append(numMtx, []float64{}) row = []float64{} row1 = numMtx1[i] for j := 0; j < array2Cols; j++ { sum = 0 for k := 0; k < array2Rows; k++ { sum += row1[k] * numMtx2[k][j] } for l := len(row); l <= j; l++ { row = append(row, 0) } row[j] = sum numMtx[i] = row } } return newMatrixFormulaArg(newFormulaArgMatrix(numMtx)) } // MOD function returns the remainder of a division between two supplied // numbers. The syntax of the function is: // // MOD(number,divisor) func (fn *formulaFuncs) MOD(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "MOD requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } divisor := argsList.Back().Value.(formulaArg).ToNumber() if divisor.Type == ArgError { return divisor } if divisor.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, "MOD divide by zero") } trunc, rem := math.Modf(number.Number / divisor.Number) if rem < 0 { trunc-- } return newNumberFormulaArg(number.Number - divisor.Number*trunc) } // MROUND function rounds a supplied number up or down to the nearest multiple // of a given number. The syntax of the function is: // // MROUND(number,multiple) func (fn *formulaFuncs) MROUND(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "MROUND requires 2 numeric arguments") } n := argsList.Front().Value.(formulaArg).ToNumber() if n.Type == ArgError { return n } multiple := argsList.Back().Value.(formulaArg).ToNumber() if multiple.Type == ArgError { return multiple } if multiple.Number == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if multiple.Number < 0 && n.Number > 0 || multiple.Number > 0 && n.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } number, res := math.Modf(n.Number / multiple.Number) if math.Trunc(res+0.5) > 0 { number++ } return newNumberFormulaArg(number * multiple.Number) } // MULTINOMIAL function calculates the ratio of the factorial of a sum of // supplied values to the product of factorials of those values. The syntax of // the function is: // // MULTINOMIAL(number1,[number2],...) func (fn *formulaFuncs) MULTINOMIAL(argsList *list.List) formulaArg { val, num, denom := 0.0, 0.0, 1.0 var err error for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: if token.String == "" { continue } if val, err = strconv.ParseFloat(token.String, 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } case ArgNumber: val = token.Number } num += val denom *= fact(val) } return newNumberFormulaArg(fact(num) / denom) } // MUNIT function returns the unit matrix for a specified dimension. The // syntax of the function is: // // MUNIT(dimension) func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "MUNIT requires 1 numeric argument") } dimension := argsList.Back().Value.(formulaArg).ToNumber() if dimension.Type == ArgError || dimension.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, dimension.Error) } matrix := make([][]formulaArg, 0, int(dimension.Number)) for i := 0; i < int(dimension.Number); i++ { row := make([]formulaArg, int(dimension.Number)) for j := 0; j < int(dimension.Number); j++ { if i == j { row[j] = newNumberFormulaArg(1.0) } else { row[j] = newNumberFormulaArg(0.0) } } matrix = append(matrix, row) } return newMatrixFormulaArg(matrix) } // ODD function ounds a supplied number away from zero (i.e. rounds a positive // number up and a negative number down), to the next odd number. The syntax // of the function is: // // ODD(number) func (fn *formulaFuncs) ODD(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ODD requires 1 numeric argument") } number := argsList.Back().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if number.Number == 0 { return newNumberFormulaArg(1) } sign := math.Signbit(number.Number) m, frac := math.Modf((number.Number - 1) / 2) val := m*2 + 1 if frac != 0 { if !sign { val += 2 } else { val -= 2 } } return newNumberFormulaArg(val) } // PI function returns the value of the mathematical constant π (pi), accurate // to 15 digits (14 decimal places). The syntax of the function is: // // PI() func (fn *formulaFuncs) PI(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "PI accepts no arguments") } return newNumberFormulaArg(math.Pi) } // POWER function calculates a given number, raised to a supplied power. // The syntax of the function is: // // POWER(number,power) func (fn *formulaFuncs) POWER(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "POWER requires 2 numeric arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type == ArgError { return x } y := argsList.Back().Value.(formulaArg).ToNumber() if y.Type == ArgError { return y } if x.Number == 0 && y.Number == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if x.Number == 0 && y.Number < 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(math.Pow(x.Number, y.Number)) } // PRODUCT function returns the product (multiplication) of a supplied set of // numerical values. The syntax of the function is: // // PRODUCT(number1,[number2],...) func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg { product := 1.0 for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: num := token.ToNumber() if num.Type != ArgNumber { return num } product = product * num.Number case ArgNumber: product = product * token.Number case ArgMatrix: for _, row := range token.Matrix { for _, cell := range row { if cell.Type == ArgNumber { product *= cell.Number } } } } } return newNumberFormulaArg(product) } // QUOTIENT function returns the integer portion of a division between two // supplied numbers. The syntax of the function is: // // QUOTIENT(numerator,denominator) func (fn *formulaFuncs) QUOTIENT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "QUOTIENT requires 2 numeric arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type == ArgError { return x } y := argsList.Back().Value.(formulaArg).ToNumber() if y.Type == ArgError { return y } if y.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(math.Trunc(x.Number / y.Number)) } // RADIANS function converts radians into degrees. The syntax of the function is: // // RADIANS(angle) func (fn *formulaFuncs) RADIANS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "RADIANS requires 1 numeric argument") } angle := argsList.Front().Value.(formulaArg).ToNumber() if angle.Type == ArgError { return angle } return newNumberFormulaArg(math.Pi / 180.0 * angle.Number) } // RAND function generates a random real number between 0 and 1. The syntax of // the function is: // // RAND() func (fn *formulaFuncs) RAND(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "RAND accepts no arguments") } return newNumberFormulaArg(rand.New(rand.NewSource(time.Now().UnixNano())).Float64()) } // RANDBETWEEN function generates a random integer between two supplied // integers. The syntax of the function is: // // RANDBETWEEN(bottom,top) func (fn *formulaFuncs) RANDBETWEEN(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "RANDBETWEEN requires 2 numeric arguments") } bottom := argsList.Front().Value.(formulaArg).ToNumber() if bottom.Type == ArgError { return bottom } top := argsList.Back().Value.(formulaArg).ToNumber() if top.Type == ArgError { return top } if top.Number < bottom.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } num := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number - bottom.Number + 1)) return newNumberFormulaArg(float64(num + int64(bottom.Number))) } // romanNumerals defined a numeral system that originated in ancient Rome and // remained the usual way of writing numbers throughout Europe well into the // Late Middle Ages. type romanNumerals struct { n float64 s string } var romanTable = [][]romanNumerals{ { {1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }, { {1000, "M"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {95, "VC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }, { {1000, "M"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }, { {1000, "M"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }, { {1000, "M"}, {999, "IM"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {499, "ID"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }, } // ROMAN function converts an arabic number to Roman. I.e. for a supplied // integer, the function returns a text string depicting the roman numeral // form of the number. The syntax of the function is: // // ROMAN(number,[form]) func (fn *formulaFuncs) ROMAN(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "ROMAN requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "ROMAN allows at most 2 arguments") } var form int number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if argsList.Len() > 1 { f := argsList.Back().Value.(formulaArg).ToNumber() if f.Type == ArgError { return f } form = int(f.Number) if form < 0 { form = 0 } else if form > 4 { form = 4 } } decimalTable := romanTable[0] switch form { case 1: decimalTable = romanTable[1] case 2: decimalTable = romanTable[2] case 3: decimalTable = romanTable[3] case 4: decimalTable = romanTable[4] } val := math.Trunc(number.Number) buf := bytes.Buffer{} for _, r := range decimalTable { for val >= r.n { buf.WriteString(r.s) val -= r.n } } return newStringFormulaArg(buf.String()) } type roundMode byte const ( closest roundMode = iota down up ) // round rounds a supplied number up or down. func (fn *formulaFuncs) round(number, digits float64, mode roundMode) float64 { var significance float64 if digits > 0 { significance = math.Pow(1/10.0, digits) } else { significance = math.Pow(10.0, -digits) } val, res := math.Modf(number / significance) switch mode { case closest: const eps = 0.499999999 if res >= eps { val++ } else if res <= -eps { val-- } case down: case up: if res > 0 { val++ } else if res < 0 { val-- } } return val * significance } // ROUND function rounds a supplied number up or down, to a specified number // of decimal places. The syntax of the function is: // // ROUND(number,num_digits) func (fn *formulaFuncs) ROUND(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "ROUND requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } digits := argsList.Back().Value.(formulaArg).ToNumber() if digits.Type == ArgError { return digits } return newNumberFormulaArg(fn.round(number.Number, digits.Number, closest)) } // ROUNDDOWN function rounds a supplied number down towards zero, to a // specified number of decimal places. The syntax of the function is: // // ROUNDDOWN(number,num_digits) func (fn *formulaFuncs) ROUNDDOWN(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "ROUNDDOWN requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } digits := argsList.Back().Value.(formulaArg).ToNumber() if digits.Type == ArgError { return digits } return newNumberFormulaArg(fn.round(number.Number, digits.Number, down)) } // ROUNDUP function rounds a supplied number up, away from zero, to a // specified number of decimal places. The syntax of the function is: // // ROUNDUP(number,num_digits) func (fn *formulaFuncs) ROUNDUP(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "ROUNDUP requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } digits := argsList.Back().Value.(formulaArg).ToNumber() if digits.Type == ArgError { return digits } return newNumberFormulaArg(fn.round(number.Number, digits.Number, up)) } // SEC function calculates the secant of a given angle. The syntax of the // function is: // // SEC(number) func (fn *formulaFuncs) SEC(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SEC requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Cos(number.Number)) } // SECH function calculates the hyperbolic secant (sech) of a supplied angle. // The syntax of the function is: // // SECH(number) func (fn *formulaFuncs) SECH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SECH requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(1 / math.Cosh(number.Number)) } // SERIESSUM function returns the sum of a power series. The syntax of the // function is: // // SERIESSUM(x,n,m,coefficients) func (fn *formulaFuncs) SERIESSUM(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "SERIESSUM requires 4 arguments") } var x, n, m formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if n = argsList.Front().Next().Value.(formulaArg).ToNumber(); n.Type != ArgNumber { return n } if m = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); m.Type != ArgNumber { return m } var result, i float64 for _, coefficient := range argsList.Back().Value.(formulaArg).ToList() { if coefficient.Value() == "" { continue } num := coefficient.ToNumber() if num.Type != ArgNumber { return num } result += num.Number * math.Pow(x.Number, n.Number+(m.Number*i)) i++ } return newNumberFormulaArg(result) } // SIGN function returns the arithmetic sign (+1, -1 or 0) of a supplied // number. I.e. if the number is positive, the Sign function returns +1, if // the number is negative, the function returns -1 and if the number is 0 // (zero), the function returns 0. The syntax of the function is: // // SIGN(number) func (fn *formulaFuncs) SIGN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SIGN requires 1 numeric argument") } val := argsList.Front().Value.(formulaArg).ToNumber() if val.Type == ArgError { return val } if val.Number < 0 { return newNumberFormulaArg(-1) } if val.Number > 0 { return newNumberFormulaArg(1) } return newNumberFormulaArg(0) } // SIN function calculates the sine of a given angle. The syntax of the // function is: // // SIN(number) func (fn *formulaFuncs) SIN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SIN requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Sin(number.Number)) } // SINH function calculates the hyperbolic sine (sinh) of a supplied number. // The syntax of the function is: // // SINH(number) func (fn *formulaFuncs) SINH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SINH requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Sinh(number.Number)) } // SQRT function calculates the positive square root of a supplied number. The // syntax of the function is: // // SQRT(number) func (fn *formulaFuncs) SQRT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SQRT requires 1 numeric argument") } value := argsList.Front().Value.(formulaArg).ToNumber() if value.Type == ArgError { return value } if value.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(math.Sqrt(value.Number)) } // SQRTPI function returns the square root of a supplied number multiplied by // the mathematical constant, π. The syntax of the function is: // // SQRTPI(number) func (fn *formulaFuncs) SQRTPI(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SQRTPI requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Sqrt(number.Number * math.Pi)) } // STDEV function calculates the sample standard deviation of a supplied set // of values. The syntax of the function is: // // STDEV(number1,[number2],...) func (fn *formulaFuncs) STDEV(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "STDEV requires at least 1 argument") } return fn.stdev(false, argsList) } // STDEVdotS function calculates the sample standard deviation of a supplied // set of values. The syntax of the function is: // // STDEV.S(number1,[number2],...) func (fn *formulaFuncs) STDEVdotS(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "STDEV.S requires at least 1 argument") } return fn.stdev(false, argsList) } // STDEVA function estimates standard deviation based on a sample. The // standard deviation is a measure of how widely values are dispersed from // the average value (the mean). The syntax of the function is: // // STDEVA(number1,[number2],...) func (fn *formulaFuncs) STDEVA(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "STDEVA requires at least 1 argument") } return fn.stdev(true, argsList) } // calcStdevPow is part of the implementation stdev. func calcStdevPow(result, count float64, n, m formulaArg) (float64, float64) { if result == -1 { result = math.Pow(n.Number-m.Number, 2) } else { result += math.Pow(n.Number-m.Number, 2) } count++ return result, count } // calcStdev is part of the implementation stdev. func calcStdev(stdeva bool, result, count float64, mean, token formulaArg) (float64, float64) { for _, row := range token.ToList() { if row.Type == ArgNumber || row.Type == ArgString { if !stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") { continue } else if stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") { num := row.ToBool() if num.Type == ArgNumber { result, count = calcStdevPow(result, count, num, mean) continue } } else { num := row.ToNumber() if num.Type == ArgNumber { result, count = calcStdevPow(result, count, num, mean) } } } } return result, count } // stdev is an implementation of the formula functions STDEV and STDEVA. func (fn *formulaFuncs) stdev(stdeva bool, argsList *list.List) formulaArg { count, result := -1.0, -1.0 var mean formulaArg if stdeva { mean = fn.AVERAGEA(argsList) } else { mean = fn.AVERAGE(argsList) } for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString, ArgNumber: if !stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") { continue } else if stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") { num := token.ToBool() if num.Type == ArgNumber { result, count = calcStdevPow(result, count, num, mean) continue } } else { num := token.ToNumber() if num.Type == ArgNumber { result, count = calcStdevPow(result, count, num, mean) } } case ArgList, ArgMatrix: result, count = calcStdev(stdeva, result, count, mean, token) } } if count > 0 && result >= 0 { return newNumberFormulaArg(math.Sqrt(result / count)) } return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } // POISSONdotDIST function calculates the Poisson Probability Mass Function or // the Cumulative Poisson Probability Function for a supplied set of // parameters. The syntax of the function is: // // POISSON.DIST(x,mean,cumulative) func (fn *formulaFuncs) POISSONdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "POISSON.DIST requires 3 arguments") } return fn.POISSON(argsList) } // POISSON function calculates the Poisson Probability Mass Function or the // Cumulative Poisson Probability Function for a supplied set of parameters. // The syntax of the function is: // // POISSON(x,mean,cumulative) func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "POISSON requires 3 arguments") } var x, mean, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if x.Number < 0 || mean.Number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if cumulative.Number == 1 { summer := 0.0 floor := math.Floor(x.Number) for i := 0; i <= int(floor); i++ { summer += math.Pow(mean.Number, float64(i)) / fact(float64(i)) } return newNumberFormulaArg(math.Exp(0-mean.Number) * summer) } return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number)) } // prepareProbArgs checking and prepare arguments for the formula function // PROB. func prepareProbArgs(argsList *list.List) []formulaArg { if argsList.Len() < 3 { return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at least 3 arguments")} } if argsList.Len() > 4 { return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at most 4 arguments")} } var lower, upper formulaArg xRange := argsList.Front().Value.(formulaArg) probRange := argsList.Front().Next().Value.(formulaArg) if lower = argsList.Front().Next().Next().Value.(formulaArg); lower.Type != ArgNumber { return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)} } upper = lower if argsList.Len() == 4 { if upper = argsList.Back().Value.(formulaArg); upper.Type != ArgNumber { return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)} } } nR1, nR2 := len(xRange.Matrix), len(probRange.Matrix) if nR1 == 0 || nR2 == 0 { return []formulaArg{newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)} } if nR1 != nR2 { return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)} } nC1, nC2 := len(xRange.Matrix[0]), len(probRange.Matrix[0]) if nC1 != nC2 { return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)} } return []formulaArg{xRange, probRange, lower, upper} } // PROB function calculates the probability associated with a given range. The // syntax of the function is: // // PROB(x_range,prob_range,lower_limit,[upper_limit]) func (fn *formulaFuncs) PROB(argsList *list.List) formulaArg { args := prepareProbArgs(argsList) if len(args) == 1 { return args[0] } xRange, probRange, lower, upper := args[0], args[1], args[2], args[3] var sum, res, fP, fW float64 var stop bool for r := 0; r < len(xRange.Matrix) && !stop; r++ { for c := 0; c < len(xRange.Matrix[0]) && !stop; c++ { p := probRange.Matrix[r][c] x := xRange.Matrix[r][c] if p.Type == ArgNumber && x.Type == ArgNumber { if fP, fW = p.Number, x.Number; fP < 0 || fP > 1 { stop = true continue } if sum += fP; fW >= lower.Number && fW <= upper.Number { res += fP } continue } return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } if stop || math.Abs(sum-1) > 1.0e-7 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(res) } // SUBTOTAL function performs a specified calculation (e.g. the sum, product, // average, etc.) for a supplied set of values. The syntax of the function is: // // SUBTOTAL(function_num,ref1,[ref2],...) func (fn *formulaFuncs) SUBTOTAL(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL requires at least 2 arguments") } var fnNum formulaArg if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber { return fnNum } subFn, ok := map[int]func(argsList *list.List) formulaArg{ 1: fn.AVERAGE, 101: fn.AVERAGE, 2: fn.COUNT, 102: fn.COUNT, 3: fn.COUNTA, 103: fn.COUNTA, 4: fn.MAX, 104: fn.MAX, 5: fn.MIN, 105: fn.MIN, 6: fn.PRODUCT, 106: fn.PRODUCT, 7: fn.STDEV, 107: fn.STDEV, 8: fn.STDEVP, 108: fn.STDEVP, 9: fn.SUM, 109: fn.SUM, 10: fn.VAR, 110: fn.VAR, 11: fn.VARP, 111: fn.VARP, }[int(fnNum.Number)] if !ok { return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL has invalid function_num") } subArgList := list.New().Init() for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { subArgList.PushBack(arg.Value.(formulaArg)) } return subFn(subArgList) } // SUM function adds together a supplied set of numbers and returns the sum of // these values. The syntax of the function is: // // SUM(number1,[number2],...) func (fn *formulaFuncs) SUM(argsList *list.List) formulaArg { var sum float64 for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgError: return token case ArgString: if num := token.ToNumber(); num.Type == ArgNumber { sum += num.Number } case ArgNumber: sum += token.Number case ArgMatrix: for _, row := range token.Matrix { for _, value := range row { if num := value.ToNumber(); num.Type == ArgNumber { sum += num.Number } } } } } return newNumberFormulaArg(sum) } // SUMIF function finds the values in a supplied array, that satisfy a given // criteria, and returns the sum of the corresponding values in a second // supplied array. The syntax of the function is: // // SUMIF(range,criteria,[sum_range]) func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 arguments") } criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg)) rangeMtx := argsList.Front().Value.(formulaArg).Matrix var sumRange [][]formulaArg if argsList.Len() == 3 { sumRange = argsList.Back().Value.(formulaArg).Matrix } var sum float64 var arg formulaArg for rowIdx, row := range rangeMtx { for colIdx, cell := range row { arg = cell if arg.Type == ArgEmpty { continue } if ok, _ := formulaCriteriaEval(arg, criteria); ok { if argsList.Len() == 3 { if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx { arg = sumRange[rowIdx][colIdx] } } if arg.Type == ArgNumber { sum += arg.Number } } } } return newNumberFormulaArg(sum) } // SUMIFS function finds values in one or more supplied arrays, that satisfy a // set of criteria, and returns the sum of the corresponding values in a // further supplied array. The syntax of the function is: // // SUMIFS(sum_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) func (fn *formulaFuncs) SUMIFS(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "SUMIFS requires at least 3 arguments") } if argsList.Len()%2 != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var args []formulaArg sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } for _, ref := range formulaIfsMatch(args) { if ref.Row >= len(sumRange) || ref.Col >= len(sumRange[ref.Row]) { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber { sum += num.Number } } return newNumberFormulaArg(sum) } // sumproduct is an implementation of the formula function SUMPRODUCT. func (fn *formulaFuncs) sumproduct(argsList *list.List) formulaArg { var ( argType ArgType n int res []float64 sum float64 ) for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) if argType == ArgUnknown { argType = token.Type } if token.Type != argType { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } switch token.Type { case ArgString, ArgNumber: if num := token.ToNumber(); num.Type == ArgNumber { sum = fn.PRODUCT(argsList).Number continue } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) case ArgMatrix: args := token.ToList() if res == nil { n = len(args) res = make([]float64, n) for i := range res { res[i] = 1.0 } } if len(args) != n { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } for i, value := range args { num := value.ToNumber() if num.Type != ArgNumber && value.Value() != "" { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } res[i] = res[i] * num.Number } } } for _, r := range res { sum += r } return newNumberFormulaArg(sum) } // SUMPRODUCT function returns the sum of the products of the corresponding // values in a set of supplied arrays. The syntax of the function is: // // SUMPRODUCT(array1,[array2],[array3],...) func (fn *formulaFuncs) SUMPRODUCT(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "SUMPRODUCT requires at least 1 argument") } for arg := argsList.Front(); arg != nil; arg = arg.Next() { if token := arg.Value.(formulaArg); token.Type == ArgError { return token } } return fn.sumproduct(argsList) } // SUMSQ function returns the sum of squares of a supplied set of values. The // syntax of the function is: // // SUMSQ(number1,[number2],...) func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg { var val, sq float64 var err error for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString: if token.String == "" { continue } if val, err = strconv.ParseFloat(token.String, 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } sq += val * val case ArgNumber: sq += token.Number * token.Number case ArgMatrix: for _, row := range token.Matrix { for _, value := range row { if value.Value() == "" { continue } if val, err = strconv.ParseFloat(value.Value(), 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } sq += val * val } } } } return newNumberFormulaArg(sq) } // sumx is an implementation of the formula functions SUMX2MY2, SUMX2PY2 and // SUMXMY2. func (fn *formulaFuncs) sumx(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) } array1 := argsList.Front().Value.(formulaArg) array2 := argsList.Back().Value.(formulaArg) left, right := array1.ToList(), array2.ToList() n := len(left) if n != len(right) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } result := 0.0 for i := 0; i < n; i++ { if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 { switch name { case "SUMX2MY2": result += lhs.Number*lhs.Number - rhs.Number*rhs.Number case "SUMX2PY2": result += lhs.Number*lhs.Number + rhs.Number*rhs.Number default: result += (lhs.Number - rhs.Number) * (lhs.Number - rhs.Number) } } } return newNumberFormulaArg(result) } // SUMX2MY2 function returns the sum of the differences of squares of two // supplied sets of values. The syntax of the function is: // // SUMX2MY2(array_x,array_y) func (fn *formulaFuncs) SUMX2MY2(argsList *list.List) formulaArg { return fn.sumx("SUMX2MY2", argsList) } // SUMX2PY2 function returns the sum of the sum of squares of two supplied sets // of values. The syntax of the function is: // // SUMX2PY2(array_x,array_y) func (fn *formulaFuncs) SUMX2PY2(argsList *list.List) formulaArg { return fn.sumx("SUMX2PY2", argsList) } // SUMXMY2 function returns the sum of the squares of differences between // corresponding values in two supplied arrays. The syntax of the function // is: // // SUMXMY2(array_x,array_y) func (fn *formulaFuncs) SUMXMY2(argsList *list.List) formulaArg { return fn.sumx("SUMXMY2", argsList) } // TAN function calculates the tangent of a given angle. The syntax of the // function is: // // TAN(number) func (fn *formulaFuncs) TAN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TAN requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Tan(number.Number)) } // TANH function calculates the hyperbolic tangent (tanh) of a supplied // number. The syntax of the function is: // // TANH(number) func (fn *formulaFuncs) TANH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TANH requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } return newNumberFormulaArg(math.Tanh(number.Number)) } // TRUNC function truncates a supplied number to a specified number of decimal // places. The syntax of the function is: // // TRUNC(number,[number_digits]) func (fn *formulaFuncs) TRUNC(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "TRUNC requires at least 1 argument") } var digits, adjust, rtrim float64 var err error number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type == ArgError { return number } if argsList.Len() > 1 { d := argsList.Back().Value.(formulaArg).ToNumber() if d.Type == ArgError { return d } digits = d.Number digits = math.Floor(digits) } adjust = math.Pow(10, digits) x := int((math.Abs(number.Number) - math.Abs(float64(int(number.Number)))) * adjust) if x != 0 { if rtrim, err = strconv.ParseFloat(strings.TrimRight(strconv.Itoa(x), "0"), 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } } if (digits > 0) && (rtrim < adjust/10) { return newNumberFormulaArg(number.Number) } return newNumberFormulaArg(float64(int(number.Number*adjust)) / adjust) } // Statistical Functions // AVEDEV function calculates the average deviation of a supplied set of // values. The syntax of the function is: // // AVEDEV(number1,[number2],...) func (fn *formulaFuncs) AVEDEV(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "AVEDEV requires at least 1 argument") } average := fn.AVERAGE(argsList) if average.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } result, count := 0.0, 0.0 for arg := argsList.Front(); arg != nil; arg = arg.Next() { num := arg.Value.(formulaArg).ToNumber() if num.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } result += math.Abs(num.Number - average.Number) count++ } return newNumberFormulaArg(result / count) } // AVERAGE function returns the arithmetic mean of a list of supplied numbers. // The syntax of the function is: // // AVERAGE(number1,[number2],...) func (fn *formulaFuncs) AVERAGE(argsList *list.List) formulaArg { var args []formulaArg for arg := argsList.Front(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } count, sum := fn.countSum(false, args) if count == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(sum / count) } // AVERAGEA function returns the arithmetic mean of a list of supplied numbers // with text cell and zero values. The syntax of the function is: // // AVERAGEA(number1,[number2],...) func (fn *formulaFuncs) AVERAGEA(argsList *list.List) formulaArg { var args []formulaArg for arg := argsList.Front(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } count, sum := fn.countSum(true, args) if count == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(sum / count) } // AVERAGEIF function finds the values in a supplied array that satisfy a // specified criteria, and returns the average (i.e. the statistical mean) of // the corresponding values in a second supplied array. The syntax of the // function is: // // AVERAGEIF(range,criteria,[average_range]) func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments") } var ( criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg)) rangeMtx = argsList.Front().Value.(formulaArg).Matrix cellRange [][]formulaArg args []formulaArg val float64 err error ok bool ) if argsList.Len() == 3 { cellRange = argsList.Back().Value.(formulaArg).Matrix } for rowIdx, row := range rangeMtx { for colIdx, col := range row { fromVal := col.Value() if fromVal == "" { continue } if col.Type == ArgString && criteria.Condition.Type != ArgString { continue } ok, _ = formulaCriteriaEval(col, criteria) if ok { if argsList.Len() == 3 { if len(cellRange) > rowIdx && len(cellRange[rowIdx]) > colIdx { fromVal = cellRange[rowIdx][colIdx].Value() } } if val, err = strconv.ParseFloat(fromVal, 64); err != nil { continue } args = append(args, newNumberFormulaArg(val)) } } } count, sum := fn.countSum(false, args) if count == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(sum / count) } // AVERAGEIFS function finds entries in one or more arrays, that satisfy a set // of supplied criteria, and returns the average (i.e. the statistical mean) // of the corresponding values in a further supplied array. The syntax of the // function is: // // AVERAGEIFS(average_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) func (fn *formulaFuncs) AVERAGEIFS(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIFS requires at least 3 arguments") } if argsList.Len()%2 != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var args []formulaArg sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } count := 0.0 for _, ref := range formulaIfsMatch(args) { if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber { sum += num.Number count++ } } if count == 0 { return newErrorFormulaArg(formulaErrorDIV, "AVERAGEIF divide by zero") } return newNumberFormulaArg(sum / count) } // getBetaHelperContFrac continued fractions for the beta function. func getBetaHelperContFrac(fX, fA, fB float64) float64 { var a1, b1, a2, b2, fnorm, cfnew, cf, rm float64 a1, b1, b2 = 1, 1, 1-(fA+fB)/(fA+1)*fX if b2 == 0 { a2, fnorm, cf = 0, 1, 1 } else { a2, fnorm = 1, 1/b2 cf = a2 * fnorm } cfnew, rm = 1, 1 fMaxIter, fMachEps := 50000.0, 2.22045e-016 bfinished := false for rm < fMaxIter && !bfinished { apl2m := fA + 2*rm d2m := rm * (fB - rm) * fX / ((apl2m - 1) * apl2m) d2m1 := -(fA + rm) * (fA + fB + rm) * fX / (apl2m * (apl2m + 1)) a1 = math.FMA(d2m, a1, a2) * fnorm b1 = math.FMA(d2m, b1, b2) * fnorm a2 = math.FMA(d2m1*a2, fnorm, a1) b2 = math.FMA(d2m1*b2, fnorm, b1) if b2 != 0 { fnorm = 1 / b2 cfnew = a2 * fnorm bfinished = math.Abs(cf-cfnew) < math.Abs(cf)*fMachEps } cf = cfnew rm++ } return cf } // getLanczosSum uses a variant of the Lanczos sum with a rational function. func getLanczosSum(fZ float64) float64 { num := []float64{ 23531376880.41075968857200767445163675473, 42919803642.64909876895789904700198885093, 35711959237.35566804944018545154716670596, 17921034426.03720969991975575445893111267, 6039542586.35202800506429164430729792107, 1439720407.311721673663223072794912393972, 248874557.8620541565114603864132294232163, 31426415.58540019438061423162831820536287, 2876370.628935372441225409051620849613599, 186056.2653952234950402949897160456992822, 8071.672002365816210638002902272250613822, 210.8242777515793458725097339207133627117, 2.506628274631000270164908177133837338626, } denom := []float64{ 0, 39916800, 120543840, 150917976, 105258076, 45995730, 13339535, 2637558, 357423, 32670, 1925, 66, 1, } var sumNum, sumDenom, zInv float64 if fZ <= 1 { sumNum = num[12] sumDenom = denom[12] for i := 11; i >= 0; i-- { sumNum = math.FMA(sumNum, fZ, num[i]) sumDenom = math.FMA(sumDenom, fZ, denom[i]) } } else { zInv = 1 / fZ sumNum = num[0] sumDenom = denom[0] for i := 1; i <= 12; i++ { sumNum = math.FMA(sumNum, zInv, num[i]) sumDenom = math.FMA(sumDenom, zInv, denom[i]) } } return sumNum / sumDenom } // getBeta return beta distribution. func getBeta(fAlpha, fBeta float64) float64 { var fA, fB float64 if fAlpha > fBeta { fA = fAlpha fB = fBeta } else { fA = fBeta fB = fAlpha } const maxGammaArgument = 171.624376956302 if fA+fB < maxGammaArgument { return math.Gamma(fA) / math.Gamma(fA+fB) * math.Gamma(fB) } fg := 6.024680040776729583740234375 fgm := fg - 0.5 fLanczos := getLanczosSum(fA) fLanczos /= getLanczosSum(fA + fB) fLanczos *= getLanczosSum(fB) fABgm := fA + fB + fgm fLanczos *= math.Sqrt((fABgm / (fA + fgm)) / (fB + fgm)) fTempA := fB / (fA + fgm) fTempB := fA / (fB + fgm) fResult := math.Exp(-fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm) fResult *= fLanczos return fResult } // getBetaDistPDF is an implementation for the Beta probability density // function. func getBetaDistPDF(fX, fA, fB float64) float64 { if fX <= 0 || fX >= 1 { return 0 } fLogDblMax, fLogDblMin := math.Log(1.79769e+308), math.Log(2.22507e-308) fLogY := math.Log(0.5 - fX + 0.5) if fX < 0.1 { fLogY = math.Log1p(-fX) } fLogX := math.Log(fX) fAm1LogX := (fA - 1) * fLogX fBm1LogY := (fB - 1) * fLogY fLogBeta := getLogBeta(fA, fB) if fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin && fBm1LogY < fLogDblMax && fBm1LogY > fLogDblMin && fLogBeta < fLogDblMax && fLogBeta > fLogDblMin && fAm1LogX+fBm1LogY < fLogDblMax && fAm1LogX+fBm1LogY > fLogDblMin { return math.Pow(fX, fA-1) * math.Pow(0.5-fX+0.5, fB-1) / getBeta(fA, fB) } return math.Exp(fAm1LogX + fBm1LogY - fLogBeta) } // getLogBeta return beta with logarithm. func getLogBeta(fAlpha, fBeta float64) float64 { var fA, fB float64 if fAlpha > fBeta { fA, fB = fAlpha, fBeta } else { fA, fB = fBeta, fAlpha } fg := 6.024680040776729583740234375 fgm := fg - 0.5 fLanczos := getLanczosSum(fA) fLanczos /= getLanczosSum(fA + fB) fLanczos *= getLanczosSum(fB) fLogLanczos := math.Log(fLanczos) fABgm := fA + fB + fgm fLogLanczos += 0.5 * (math.Log(fABgm) - math.Log(fA+fgm) - math.Log(fB+fgm)) fTempA := fB / (fA + fgm) fTempB := fA / (fB + fgm) fResult := -fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm fResult += fLogLanczos return fResult } // getBetaDist is an implementation for the beta distribution function. func getBetaDist(fXin, fAlpha, fBeta float64) float64 { if fXin <= 0 { return 0 } if fXin >= 1 { return 1 } if fBeta == 1 { return math.Pow(fXin, fAlpha) } if fAlpha == 1 { return -math.Expm1(fBeta * math.Log1p(-fXin)) } var fResult float64 fY, flnY := (0.5-fXin)+0.5, math.Log1p(-fXin) fX, flnX := fXin, math.Log(fXin) fA, fB := fAlpha, fBeta bReflect := fXin > fAlpha/(fAlpha+fBeta) if bReflect { fA = fBeta fB = fAlpha fX = fY fY = fXin flnX = flnY flnY = math.Log(fXin) } fResult = getBetaHelperContFrac(fX, fA, fB) / fA fP, fQ := fA/(fA+fB), fB/(fA+fB) var fTemp float64 if fA > 1 && fB > 1 && fP < 0.97 && fQ < 0.97 { fTemp = getBetaDistPDF(fX, fA, fB) * fX * fY } else { fTemp = math.Exp(fA*flnX + fB*flnY - getLogBeta(fA, fB)) } fResult *= fTemp if bReflect { fResult = 0.5 - fResult + 0.5 } return fResult } // prepareBETAdotDISTArgs checking and prepare arguments for the formula // function BETA.DIST. func (fn *formulaFuncs) prepareBETAdotDISTArgs(argsList *list.List) formulaArg { if argsList.Len() < 4 { return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at least 4 arguments") } if argsList.Len() > 6 { return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at most 6 arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() if alpha.Type != ArgNumber { return alpha } beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if beta.Type != ArgNumber { return beta } if alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } cumulative := argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool() if cumulative.Type != ArgNumber { return cumulative } a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) if argsList.Len() > 4 { if a = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { return a } } if argsList.Len() == 6 { if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { return b } } return newListFormulaArg([]formulaArg{x, alpha, beta, cumulative, a, b}) } // BETAdotDIST function calculates the cumulative beta distribution function // or the probability density function of the Beta distribution, for a // supplied set of parameters. The syntax of the function is: // // BETA.DIST(x,alpha,beta,cumulative,[A],[B]) func (fn *formulaFuncs) BETAdotDIST(argsList *list.List) formulaArg { args := fn.prepareBETAdotDISTArgs(argsList) if args.Type != ArgList { return args } x, alpha, beta, cumulative, a, b := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] if x.Number < a.Number || x.Number > b.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if a.Number == b.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } scale := b.Number - a.Number x.Number = (x.Number - a.Number) / scale if cumulative.Number == 1 { return newNumberFormulaArg(getBetaDist(x.Number, alpha.Number, beta.Number)) } return newNumberFormulaArg(getBetaDistPDF(x.Number, alpha.Number, beta.Number) / scale) } // BETADIST function calculates the cumulative beta probability density // function for a supplied set of parameters. The syntax of the function is: // // BETADIST(x,alpha,beta,[A],[B]) func (fn *formulaFuncs) BETADIST(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at most 5 arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() if alpha.Type != ArgNumber { return alpha } beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if beta.Type != ArgNumber { return beta } if alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) if argsList.Len() > 3 { if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { return a } } if argsList.Len() == 5 { if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { return b } } if x.Number < a.Number || x.Number > b.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if a.Number == b.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(getBetaDist((x.Number-a.Number)/(b.Number-a.Number), alpha.Number, beta.Number)) } // d1mach returns double precision real machine constants. func d1mach(i int) float64 { arr := []float64{ 2.2250738585072014e-308, 1.7976931348623158e+308, 1.1102230246251565e-16, 2.2204460492503131e-16, 0.301029995663981195, } if i > len(arr) { return 0 } return arr[i-1] } // chebyshevInit determines the number of terms for the double precision // orthogonal series "dos" needed to insure the error is no larger // than "eta". Ordinarily eta will be chosen to be one-tenth machine // precision. func chebyshevInit(nos int, eta float64, dos []float64) int { i, e := 0, 0.0 if nos < 1 { return 0 } for ii := 1; ii <= nos; ii++ { i = nos - ii e += math.Abs(dos[i]) if e > eta { return i } } return i } // chebyshevEval evaluates the n-term Chebyshev series "a" at "x". func chebyshevEval(n int, x float64, a []float64) float64 { if n < 1 || n > 1000 || x < -1.1 || x > 1.1 { return math.NaN() } twox, b0, b1, b2 := x*2, 0.0, 0.0, 0.0 for i := 1; i <= n; i++ { b2 = b1 b1 = b0 b0 = twox*b1 - b2 + a[n-i] } return (b0 - b2) * 0.5 } // lgammacor is an implementation for the log(gamma) correction. func lgammacor(x float64) float64 { algmcs := []float64{ 0.1666389480451863247205729650822, -0.1384948176067563840732986059135e-4, 0.9810825646924729426157171547487e-8, -0.1809129475572494194263306266719e-10, 0.6221098041892605227126015543416e-13, -0.3399615005417721944303330599666e-15, 0.2683181998482698748957538846666e-17, -0.2868042435334643284144622399999e-19, 0.3962837061046434803679306666666e-21, -0.6831888753985766870111999999999e-23, 0.1429227355942498147573333333333e-24, -0.3547598158101070547199999999999e-26, 0.1025680058010470912000000000000e-27, -0.3401102254316748799999999999999e-29, 0.1276642195630062933333333333333e-30, } nalgm := chebyshevInit(15, d1mach(3), algmcs) xbig := 1.0 / math.Sqrt(d1mach(3)) xmax := math.Exp(math.Min(math.Log(d1mach(2)/12.0), -math.Log(12.0*d1mach(1)))) if x < 10.0 { return math.NaN() } else if x >= xmax { return 4.930380657631324e-32 } else if x < xbig { tmp := 10.0 / x return chebyshevEval(nalgm, tmp*tmp*2.0-1.0, algmcs) / x } return 1.0 / (x * 12.0) } // logrelerr compute the relative error logarithm. func logrelerr(x float64) float64 { alnrcs := []float64{ 0.10378693562743769800686267719098e+1, -0.13364301504908918098766041553133, 0.19408249135520563357926199374750e-1, -0.30107551127535777690376537776592e-2, 0.48694614797154850090456366509137e-3, -0.81054881893175356066809943008622e-4, 0.13778847799559524782938251496059e-4, -0.23802210894358970251369992914935e-5, 0.41640416213865183476391859901989e-6, -0.73595828378075994984266837031998e-7, 0.13117611876241674949152294345011e-7, -0.23546709317742425136696092330175e-8, 0.42522773276034997775638052962567e-9, -0.77190894134840796826108107493300e-10, 0.14075746481359069909215356472191e-10, -0.25769072058024680627537078627584e-11, 0.47342406666294421849154395005938e-12, -0.87249012674742641745301263292675e-13, 0.16124614902740551465739833119115e-13, -0.29875652015665773006710792416815e-14, 0.55480701209082887983041321697279e-15, -0.10324619158271569595141333961932e-15, 0.19250239203049851177878503244868e-16, -0.35955073465265150011189707844266e-17, 0.67264542537876857892194574226773e-18, -0.12602624168735219252082425637546e-18, 0.23644884408606210044916158955519e-19, -0.44419377050807936898878389179733e-20, 0.83546594464034259016241293994666e-21, -0.15731559416479562574899253521066e-21, 0.29653128740247422686154369706666e-22, -0.55949583481815947292156013226666e-23, 0.10566354268835681048187284138666e-23, -0.19972483680670204548314999466666e-24, 0.37782977818839361421049855999999e-25, -0.71531586889081740345038165333333e-26, 0.13552488463674213646502024533333e-26, -0.25694673048487567430079829333333e-27, 0.48747756066216949076459519999999e-28, -0.92542112530849715321132373333333e-29, 0.17578597841760239233269760000000e-29, -0.33410026677731010351377066666666e-30, 0.63533936180236187354180266666666e-31, } nlnrel := chebyshevInit(43, 0.1*d1mach(3), alnrcs) if x <= -1 { return math.NaN() } if math.Abs(x) <= 0.375 { return x * (1.0 - x*chebyshevEval(nlnrel, x/0.375, alnrcs)) } return math.Log(x + 1.0) } // logBeta is an implementation for the log of the beta distribution // function. func logBeta(a, b float64) float64 { corr, p, q := 0.0, a, a if b < p { p = b } if b > q { q = b } if p < 0 { return math.NaN() } if p == 0 { return math.MaxFloat64 } if p >= 10.0 { corr = lgammacor(p) + lgammacor(q) - lgammacor(p+q) f1 := q * logrelerr(-p/(p+q)) return math.Log(q)*-0.5 + 0.918938533204672741780329736406 + corr + (p-0.5)*math.Log(p/(p+q)) + math.Nextafter(f1, f1) } if q >= 10 { corr = lgammacor(q) - lgammacor(p+q) val, _ := math.Lgamma(p) return val + corr + p - p*math.Log(p+q) + (q-0.5)*logrelerr(-p/(p+q)) } return math.Log(math.Gamma(p) * (math.Gamma(q) / math.Gamma(p+q))) } // pbetaRaw is a part of pbeta for the beta distribution. func pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y float64) float64 { if q > 1.0 { xb := p*math.Log(y) + q*math.Log(1.0-y) - logBeta(p, q) - math.Log(q) ib := int(math.Max(xb/alnsml, 0.0)) term := math.Exp(xb - float64(ib)*alnsml) c := 1.0 / (1.0 - y) p1 := q * c / (p + q - 1.0) finsum := 0.0 n := int(q) if q == float64(n) { n = n - 1 } for i := 1; i <= n; i++ { if p1 <= 1 && term/eps <= finsum { break } xi := float64(i) term = (q - xi + 1.0) * c * term / (p + q - xi) if term > 1.0 { ib = ib - 1 term = term * sml } if ib == 0 { finsum = finsum + term } } ans = ans + finsum } if y != x || p != pin { ans = 1.0 - ans } ans = math.Max(math.Min(ans, 1.0), 0.0) return ans } // pbeta returns distribution function of the beta distribution. func pbeta(x, pin, qin float64) (ans float64) { eps := d1mach(3) alneps := math.Log(eps) sml := d1mach(1) alnsml := math.Log(sml) y := x p := pin q := qin if p/(p+q) < x { y = 1.0 - y p = qin q = pin } if (p+q)*y/(p+1.0) < eps { xb := p*math.Log(math.Max(y, sml)) - math.Log(p) - logBeta(p, q) if xb > alnsml && y != 0.0 { ans = math.Exp(xb) } if y != x || p != pin { ans = 1.0 - ans } } else { ps := q - math.Floor(q) if ps == 0.0 { ps = 1.0 } xb := p*math.Log(y) - logBeta(ps, p) - math.Log(p) if xb >= alnsml { ans = math.Exp(xb) term := ans * p if ps != 1.0 { n := int(math.Max(alneps/math.Log(y), 4.0)) for i := 1; i <= n; i++ { xi := float64(i) term = term * (xi - ps) * y / xi ans = ans + term/(p+xi) } } } ans = pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y) } return ans } // betainvProbIterator is a part of betainv for the inverse of the beta // function. func betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBeta, maxCumulative, prob1, prob2 float64) float64 { var i, j, prev, prop4 float64 j = 1 for prob := 0; prob < 1000; prob++ { prop3 := pbeta(beta3, alpha1, beta1) prop3 = (prop3 - prob1) * math.Exp(logBeta+prob2*math.Log(beta3)+beta2*math.Log(1.0-beta3)) if prop3*prop4 <= 0 { prev = math.Max(math.Abs(j), maxCumulative) } h := 1.0 for iteratorCount := 0; iteratorCount < 1000; iteratorCount++ { j = h * prop3 if math.Abs(j) < prev { i = beta3 - j if i >= 0 && i <= 1.0 { if prev <= alpha3 { return beta3 } if math.Abs(prop3) <= alpha3 { return beta3 } if i != 0 && i != 1.0 { break } } } h /= 3.0 } if i == beta3 { return beta3 } beta3, prop4 = i, prop3 } return beta3 } // calcBetainv is an implementation for the quantile of the beta // distribution. func calcBetainv(probability, alpha, beta, lower, upper float64) float64 { minCumulative, maxCumulative := 1.0e-300, 3.0e-308 lowerBound, upperBound := maxCumulative, 1.0-2.22e-16 needSwap := false var alpha1, alpha2, beta1, beta2, beta3, prob1, x, y float64 if probability <= 0.5 { prob1, alpha1, beta1 = probability, alpha, beta } else { prob1, alpha1, beta1, needSwap = 1.0-probability, beta, alpha, true } logBetaNum := logBeta(alpha, beta) prob2 := math.Sqrt(-math.Log(prob1 * prob1)) prob3 := prob2 - (prob2*0.27061+2.3075)/(prob2*(prob2*0.04481+0.99229)+1) if alpha1 > 1 && beta1 > 1 { alpha2, beta2, prob2 = 1/(alpha1+alpha1-1), 1/(beta1+beta1-1), (prob3*prob3-3)/6 x = 2 / (alpha2 + beta2) y = prob3*math.Sqrt(x+prob2)/x - (beta2-alpha2)*(prob2+5/6.0-2/(x*3)) beta3 = alpha1 / (alpha1 + beta1*math.Exp(y+y)) } else { beta2, prob2 = 1/(beta1*9), beta1+beta1 beta2 = prob2 * math.Pow(1-beta2+prob3*math.Sqrt(beta2), 3) if beta2 <= 0 { beta3 = 1 - math.Exp((math.Log((1-prob1)*beta1)+logBetaNum)/beta1) } else { beta2 = (prob2 + alpha1*4 - 2) / beta2 if beta2 <= 1 { beta3 = math.Exp((logBetaNum + math.Log(alpha1*prob1)) / alpha1) } else { beta3 = 1 - 2/(beta2+1) } } } beta2, prob2 = 1-beta1, 1-alpha1 if beta3 < lowerBound { beta3 = lowerBound } else if beta3 > upperBound { beta3 = upperBound } alpha3 := math.Max(minCumulative, math.Pow(10.0, -13.0-2.5/(alpha1*alpha1)-0.5/(prob1*prob1))) beta3 = betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBetaNum, maxCumulative, prob1, prob2) if needSwap { beta3 = 1.0 - beta3 } return (upper-lower)*beta3 + lower } // betainv is an implementation of the formula functions BETAINV and // BETA.INV. func (fn *formulaFuncs) betainv(name string, argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name)) } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 5 arguments", name)) } probability := argsList.Front().Value.(formulaArg).ToNumber() if probability.Type != ArgNumber { return probability } if probability.Number <= 0 || probability.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() if alpha.Type != ArgNumber { return alpha } beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if beta.Type != ArgNumber { return beta } if alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) if argsList.Len() > 3 { if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { return a } } if argsList.Len() == 5 { if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { return b } } if a.Number == b.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(calcBetainv(probability.Number, alpha.Number, beta.Number, a.Number, b.Number)) } // BETAINV function uses an iterative procedure to calculate the inverse of // the cumulative beta probability density function for a supplied // probability. The syntax of the function is: // // BETAINV(probability,alpha,beta,[A],[B]) func (fn *formulaFuncs) BETAINV(argsList *list.List) formulaArg { return fn.betainv("BETAINV", argsList) } // BETAdotINV function uses an iterative procedure to calculate the inverse of // the cumulative beta probability density function for a supplied // probability. The syntax of the function is: // // BETA.INV(probability,alpha,beta,[A],[B]) func (fn *formulaFuncs) BETAdotINV(argsList *list.List) formulaArg { return fn.betainv("BETA.INV", argsList) } // incompleteGamma is an implementation of the incomplete gamma function. func incompleteGamma(a, x float64) float64 { maxVal := 32 summer := 0.0 for n := 0; n <= maxVal; n++ { divisor := a for i := 1; i <= n; i++ { divisor *= a + float64(i) } summer += math.Pow(x, float64(n)) / divisor } return math.Pow(x, a) * math.Exp(0-x) * summer } // binomCoeff implement binomial coefficient calculation. func binomCoeff(n, k float64) float64 { return fact(n) / (fact(k) * fact(n-k)) } // binomdist implement binomial distribution calculation. func binomdist(x, n, p float64) float64 { return binomCoeff(n, x) * math.Pow(p, x) * math.Pow(1-p, n-x) } // BINOMdotDIST function returns the Binomial Distribution probability for a // given number of successes from a specified number of trials. The syntax of // the function is: // // BINOM.DIST(number_s,trials,probability_s,cumulative) func (fn *formulaFuncs) BINOMdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST requires 4 arguments") } return fn.BINOMDIST(argsList) } // BINOMDIST function returns the Binomial Distribution probability of a // specified number of successes out of a specified number of trials. The // syntax of the function is: // // BINOMDIST(number_s,trials,probability_s,cumulative) func (fn *formulaFuncs) BINOMDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "BINOMDIST requires 4 arguments") } var s, trials, probability, cumulative formulaArg if s = argsList.Front().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { return s } if trials = argsList.Front().Next().Value.(formulaArg).ToNumber(); trials.Type != ArgNumber { return trials } if s.Number < 0 || s.Number > trials.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if probability = argsList.Back().Prev().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if probability.Number < 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if cumulative.Number == 1 { bm := 0.0 for i := 0; i <= int(s.Number); i++ { bm += binomdist(float64(i), trials.Number, probability.Number) } return newNumberFormulaArg(bm) } return newNumberFormulaArg(binomdist(s.Number, trials.Number, probability.Number)) } // BINOMdotDISTdotRANGE function returns the Binomial Distribution probability // for the number of successes from a specified number of trials falling into // a specified range. // // BINOM.DIST.RANGE(trials,probability_s,number_s,[number_s2]) func (fn *formulaFuncs) BINOMdotDISTdotRANGE(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at least 3 arguments") } if argsList.Len() > 4 { return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at most 4 arguments") } trials := argsList.Front().Value.(formulaArg).ToNumber() if trials.Type != ArgNumber { return trials } probability := argsList.Front().Next().Value.(formulaArg).ToNumber() if probability.Type != ArgNumber { return probability } if probability.Number < 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } num1 := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if num1.Type != ArgNumber { return num1 } if num1.Number < 0 || num1.Number > trials.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } num2 := num1 if argsList.Len() > 3 { if num2 = argsList.Back().Value.(formulaArg).ToNumber(); num2.Type != ArgNumber { return num2 } } if num2.Number < 0 || num2.Number > trials.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } sum := 0.0 for i := num1.Number; i <= num2.Number; i++ { sum += binomdist(i, trials.Number, probability.Number) } return newNumberFormulaArg(sum) } // binominv implement inverse of the binomial distribution calculation. func binominv(n, p, alpha float64) float64 { q, i, sum, maxVal := 1-p, 0.0, 0.0, 0.0 n = math.Floor(n) if q > p { factor := math.Pow(q, n) sum = factor for i = 0; i < n && sum < alpha; i++ { factor *= (n - i) / (i + 1) * p / q sum += factor } return i } factor := math.Pow(p, n) sum, maxVal = 1-factor, n for i = 0; i < maxVal && sum >= alpha; i++ { factor *= (n - i) / (i + 1) * q / p sum -= factor } return n - i } // BINOMdotINV function returns the inverse of the Cumulative Binomial // Distribution. The syntax of the function is: // // BINOM.INV(trials,probability_s,alpha) func (fn *formulaFuncs) BINOMdotINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "BINOM.INV requires 3 numeric arguments") } trials := argsList.Front().Value.(formulaArg).ToNumber() if trials.Type != ArgNumber { return trials } if trials.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } probability := argsList.Front().Next().Value.(formulaArg).ToNumber() if probability.Type != ArgNumber { return probability } if probability.Number <= 0 || probability.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } alpha := argsList.Back().Value.(formulaArg).ToNumber() if alpha.Type != ArgNumber { return alpha } if alpha.Number <= 0 || alpha.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(binominv(trials.Number, probability.Number, alpha.Number)) } // CHIDIST function calculates the right-tailed probability of the chi-square // distribution. The syntax of the function is: // // CHIDIST(x,degrees_freedom) func (fn *formulaFuncs) CHIDIST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHIDIST requires 2 numeric arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } degrees := argsList.Back().Value.(formulaArg).ToNumber() if degrees.Type != ArgNumber { return degrees } logSqrtPi, sqrtPi := math.Log(math.Sqrt(math.Pi)), 1/math.Sqrt(math.Pi) var e, s, z, c, y float64 a, x1, even := x.Number/2, x.Number, int(degrees.Number)%2 == 0 if degrees.Number > 1 { y = math.Exp(-a) } args := list.New() args.PushBack(newNumberFormulaArg(-math.Sqrt(x1))) o := fn.NORMSDIST(args) s = 2 * o.Number if even { s = y } if degrees.Number > 2 { x1 = (degrees.Number - 1) / 2 z = 0.5 if even { z = 1 } if a > 20 { e = logSqrtPi if even { e = 0 } c = math.Log(a) for z <= x1 { e = math.Log(z) + e s += math.Exp(c*z - a - e) z++ } return newNumberFormulaArg(s) } e = sqrtPi / math.Sqrt(a) if even { e = 1 } c = 0 for z <= x1 { e = e * (a / z) c = c + e z++ } return newNumberFormulaArg(c*y + s) } return newNumberFormulaArg(s) } // CHIINV function calculates the inverse of the right-tailed probability of // the Chi-Square Distribution. The syntax of the function is: // // CHIINV(probability,deg_freedom) func (fn *formulaFuncs) CHIINV(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHIINV requires 2 numeric arguments") } probability := argsList.Front().Value.(formulaArg).ToNumber() if probability.Type != ArgNumber { return probability } if probability.Number <= 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } deg := argsList.Back().Value.(formulaArg).ToNumber() if deg.Type != ArgNumber { return deg } if deg.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(gammainv(1-probability.Number, 0.5*deg.Number, 2.0)) } // CHITEST function uses the chi-square test to calculate the probability that // the differences between two supplied data sets (of observed and expected // frequencies), are likely to be simply due to sampling error, or if they are // likely to be real. The syntax of the function is: // // CHITEST(actual_range,expected_range) func (fn *formulaFuncs) CHITEST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHITEST requires 2 arguments") } actual, expected := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg) actualList, expectedList := actual.ToList(), expected.ToList() rows := len(actual.Matrix) if rows == 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } columns := len(actualList) / rows if len(actualList) != len(expectedList) || len(actualList) == 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var result float64 var degrees int for i := 0; i < len(actualList); i++ { a, e := actualList[i].ToNumber(), expectedList[i].ToNumber() if a.Type == ArgNumber && e.Type == ArgNumber { if e.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } if e.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } result += (a.Number - e.Number) * (a.Number - e.Number) / e.Number } } if rows == 1 { degrees = columns - 1 } else if columns == 1 { degrees = rows - 1 } else { degrees = (columns - 1) * (rows - 1) } args := list.New() args.PushBack(newNumberFormulaArg(result)) args.PushBack(newNumberFormulaArg(float64(degrees))) return fn.CHIDIST(args) } // getGammaSeries calculates a power-series of the gamma function. func getGammaSeries(fA, fX float64) float64 { var ( fHalfMachEps = 2.22045e-016 / 2 fDenomfactor = fA fSummand = 1 / fA fSum = fSummand nCount = 1 ) for fSummand/fSum > fHalfMachEps && nCount <= 10000 { fDenomfactor = fDenomfactor + 1 fSummand = fSummand * fX / fDenomfactor fSum = fSum + fSummand nCount = nCount + 1 } return fSum } // getGammaContFraction returns continued fraction with odd items of the gamma // function. func getGammaContFraction(fA, fX float64) float64 { var ( fBigInv = 2.22045e-016 fHalfMachEps = fBigInv / 2 fBig = 1 / fBigInv fCount = 0.0 fY = 1 - fA fDenom = fX + 2 - fA fPkm1 = fX + 1 fPkm2 = 1.0 fQkm1 = fDenom * fX fQkm2 = fX fApprox = fPkm1 / fQkm1 bFinished = false ) for !bFinished && fCount < 10000 { fCount = fCount + 1 fY = fY + 1 fDenom = fDenom + 2 var ( fNum = fY * fCount f1 = fPkm1 * fDenom f2 = fPkm2 * fNum fPk = math.Nextafter(f1, f1) - math.Nextafter(f2, f2) f3 = fQkm1 * fDenom f4 = fQkm2 * fNum fQk = math.Nextafter(f3, f3) - math.Nextafter(f4, f4) ) if fQk != 0 { fR := fPk / fQk bFinished = math.Abs((fApprox-fR)/fR) <= fHalfMachEps fApprox = fR } fPkm2, fPkm1, fQkm2, fQkm1 = fPkm1, fPk, fQkm1, fQk if math.Abs(fPk) > fBig { // reduce a fraction does not change the value fPkm2 = fPkm2 * fBigInv fPkm1 = fPkm1 * fBigInv fQkm2 = fQkm2 * fBigInv fQkm1 = fQkm1 * fBigInv } } return fApprox } // getLogGammaHelper is a part of implementation of the function getLogGamma. func getLogGammaHelper(fZ float64) float64 { _fg := 6.024680040776729583740234375 zgHelp := fZ + _fg - 0.5 return math.Log(getLanczosSum(fZ)) + (fZ-0.5)*math.Log(zgHelp) - zgHelp } // getGammaHelper is a part of implementation of the function getLogGamma. func getGammaHelper(fZ float64) float64 { var ( gamma = getLanczosSum(fZ) fg = 6.024680040776729583740234375 zgHelp = fZ + fg - 0.5 // avoid intermediate overflow halfpower = math.Pow(zgHelp, fZ/2-0.25) ) gamma *= halfpower gamma /= math.Exp(zgHelp) gamma *= halfpower if fZ <= 20 && fZ == math.Floor(fZ) { gamma = math.Round(gamma) } return gamma } // getLogGamma calculates the natural logarithm of the gamma function. func getLogGamma(fZ float64) float64 { fMaxGammaArgument := 171.624376956302 if fZ >= fMaxGammaArgument { return getLogGammaHelper(fZ) } if fZ >= 1.0 { return math.Log(getGammaHelper(fZ)) } if fZ >= 0.5 { return math.Log(getGammaHelper(fZ+1) / fZ) } return getLogGammaHelper(fZ+2) - math.Log(fZ+1) - math.Log(fZ) } // getLowRegIGamma returns lower regularized incomplete gamma function. func getLowRegIGamma(fA, fX float64) float64 { lnFactor := fA*math.Log(fX) - fX - getLogGamma(fA) factor := math.Exp(lnFactor) if fX > fA+1 { return 1 - factor*getGammaContFraction(fA, fX) } return factor * getGammaSeries(fA, fX) } // getChiSqDistCDF returns left tail for the Chi-Square distribution. func getChiSqDistCDF(fX, fDF float64) float64 { if fX <= 0 { return 0 } return getLowRegIGamma(fDF/2, fX/2) } // getChiSqDistPDF calculates the probability density function for the // Chi-Square distribution. func getChiSqDistPDF(fX, fDF float64) float64 { if fDF*fX > 1391000 { return math.Exp((0.5*fDF-1)*math.Log(fX*0.5) - 0.5*fX - math.Log(2) - getLogGamma(0.5*fDF)) } var fCount, fValue float64 if math.Mod(fDF, 2) < 0.5 { fValue = 0.5 fCount = 2 } else { fValue = 1 / math.Sqrt(fX*2*math.Pi) fCount = 1 } for fCount < fDF { fValue *= fX / fCount fCount += 2 } if fX >= 1425 { fValue = math.Exp(math.Log(fValue) - fX/2) } else { fValue *= math.Exp(-fX / 2) } return fValue } // CHISQdotDIST function calculates the Probability Density Function or the // Cumulative Distribution Function for the Chi-Square Distribution. The // syntax of the function is: // // CHISQ.DIST(x,degrees_freedom,cumulative) func (fn *formulaFuncs) CHISQdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST requires 3 arguments") } var x, degrees, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if x.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } maxDeg := math.Pow10(10) if degrees.Number < 1 || degrees.Number >= maxDeg { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 1 { return newNumberFormulaArg(getChiSqDistCDF(x.Number, degrees.Number)) } return newNumberFormulaArg(getChiSqDistPDF(x.Number, degrees.Number)) } // CHISQdotDISTdotRT function calculates the right-tailed probability of the // Chi-Square Distribution. The syntax of the function is: // // CHISQ.DIST.RT(x,degrees_freedom) func (fn *formulaFuncs) CHISQdotDISTdotRT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST.RT requires 2 numeric arguments") } return fn.CHIDIST(argsList) } // CHISQdotTEST function performs the chi-square test on two supplied data sets // (of observed and expected frequencies), and returns the probability that // the differences between the sets are simply due to sampling error. The // syntax of the function is: // // CHISQ.TEST(actual_range,expected_range) func (fn *formulaFuncs) CHISQdotTEST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.TEST requires 2 arguments") } return fn.CHITEST(argsList) } // hasChangeOfSign check if the sign has been changed. func hasChangeOfSign(u, w float64) bool { return (u < 0 && w > 0) || (u > 0 && w < 0) } // calcInverseIterator directly maps the required parameters for inverse // distribution functions. type calcInverseIterator struct { name string fp, fDF, nT float64 } // callBack implements the callback function for the inverse iterator. func (iterator *calcInverseIterator) callBack(x float64) float64 { if iterator.name == "CHISQ.INV" { return iterator.fp - getChiSqDistCDF(x, iterator.fDF) } return iterator.fp - getTDist(x, iterator.fDF, iterator.nT) } // inverseQuadraticInterpolation inverse quadratic interpolation with // additional brackets. func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx, fBy float64) float64 { fYEps := 1.0e-307 fXEps := 2.22045e-016 fPx, fPy, fQx, fQy, fRx, fRy := fAx, fAy, fBx, fBy, fAx, fAy fSx := 0.5 * (fAx + fBx) bHasToInterpolate := true nCount := 0 for nCount < 500 && math.Abs(fRy) > fYEps && (fBx-fAx) > math.Max(math.Abs(fAx), math.Abs(fBx))*fXEps { if bHasToInterpolate { if fPy != fQy && fQy != fRy && fRy != fPy { fSx = fPx*fRy*fQy/(fRy-fPy)/(fQy-fPy) + fRx*fQy*fPy/(fQy-fRy)/(fPy-fRy) + fQx*fPy*fRy/(fPy-fQy)/(fRy-fQy) bHasToInterpolate = (fAx < fSx) && (fSx < fBx) } else { bHasToInterpolate = false } } if !bHasToInterpolate { fSx = 0.5 * (fAx + fBx) fQx, fQy = fBx, fBy bHasToInterpolate = true } fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy fRy = iterator.callBack(fSx) if hasChangeOfSign(fAy, fRy) { fBx, fBy = fRx, fRy } else { fAx, fAy = fRx, fRy } bHasToInterpolate = bHasToInterpolate && (math.Abs(fRy)*2 <= math.Abs(fQy)) nCount++ } return fRx } // calcIterateInverse function calculates the iteration for inverse // distributions. func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 { fAy, fBy := iterator.callBack(fAx), iterator.callBack(fBx) var fTemp float64 var nCount int for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ { if math.Abs(fAy) <= math.Abs(fBy) { fTemp = fAx fAx += 2 * (fAx - fBx) if fAx < 0 { fAx = 0 } fBx = fTemp fBy = fAy fAy = iterator.callBack(fAx) } else { fTemp = fBx fBx += 2 * (fBx - fAx) fAx = fTemp fAy = fBy fBy = iterator.callBack(fBx) } } if fAy == 0 || fBy == 0 { return 0 } return inverseQuadraticInterpolation(iterator, fAx, fAy, fBx, fBy) } // CHISQdotINV function calculates the inverse of the left-tailed probability // of the Chi-Square Distribution. The syntax of the function is: // // CHISQ.INV(probability,degrees_freedom) func (fn *formulaFuncs) CHISQdotINV(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV requires 2 numeric arguments") } var probability, degrees formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if probability.Number < 0 || probability.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if degrees.Number < 1 || degrees.Number > math.Pow10(10) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ name: "CHISQ.INV", fp: probability.Number, fDF: degrees.Number, }, degrees.Number/2, degrees.Number)) } // CHISQdotINVdotRT function calculates the inverse of the right-tailed // probability of the Chi-Square Distribution. The syntax of the function is: // // CHISQ.INV.RT(probability,degrees_freedom) func (fn *formulaFuncs) CHISQdotINVdotRT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV.RT requires 2 numeric arguments") } return fn.CHIINV(argsList) } // confidence is an implementation of the formula functions CONFIDENCE and // CONFIDENCE.NORM. func (fn *formulaFuncs) confidence(name string, argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 numeric arguments", name)) } alpha := argsList.Front().Value.(formulaArg).ToNumber() if alpha.Type != ArgNumber { return alpha } if alpha.Number <= 0 || alpha.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } stdDev := argsList.Front().Next().Value.(formulaArg).ToNumber() if stdDev.Type != ArgNumber { return stdDev } if stdDev.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } size := argsList.Back().Value.(formulaArg).ToNumber() if size.Type != ArgNumber { return size } if size.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New() args.Init() args.PushBack(newNumberFormulaArg(alpha.Number / 2)) args.PushBack(newNumberFormulaArg(0)) args.PushBack(newNumberFormulaArg(1)) return newNumberFormulaArg(-fn.NORMINV(args).Number * (stdDev.Number / math.Sqrt(size.Number))) } // CONFIDENCE function uses a Normal Distribution to calculate a confidence // value that can be used to construct the Confidence Interval for a // population mean, for a supplied probability and sample size. It is assumed // that the standard deviation of the population is known. The syntax of the // function is: // // CONFIDENCE(alpha,standard_dev,size) func (fn *formulaFuncs) CONFIDENCE(argsList *list.List) formulaArg { return fn.confidence("CONFIDENCE", argsList) } // CONFIDENCEdotNORM function uses a Normal Distribution to calculate a // confidence value that can be used to construct the confidence interval for // a population mean, for a supplied probability and sample size. It is // assumed that the standard deviation of the population is known. The syntax // of the function is: // // CONFIDENCE.NORM(alpha,standard_dev,size) func (fn *formulaFuncs) CONFIDENCEdotNORM(argsList *list.List) formulaArg { return fn.confidence("CONFIDENCE.NORM", argsList) } // CONFIDENCEdotT function uses a Student's T-Distribution to calculate a // confidence value that can be used to construct the confidence interval for // a population mean, for a supplied probablity and supplied sample size. It // is assumed that the standard deviation of the population is known. The // syntax of the function is: // // CONFIDENCE.T(alpha,standard_dev,size) func (fn *formulaFuncs) CONFIDENCEdotT(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "CONFIDENCE.T requires 3 arguments") } var alpha, standardDev, size formulaArg if alpha = argsList.Front().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { return alpha } if standardDev = argsList.Front().Next().Value.(formulaArg).ToNumber(); standardDev.Type != ArgNumber { return standardDev } if size = argsList.Back().Value.(formulaArg).ToNumber(); size.Type != ArgNumber { return size } if alpha.Number <= 0 || alpha.Number >= 1 || standardDev.Number <= 0 || size.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if size.Number == 1 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(standardDev.Number * calcIterateInverse(calcInverseIterator{ name: "CONFIDENCE.T", fp: alpha.Number, fDF: size.Number - 1, nT: 2, }, size.Number/2, size.Number) / math.Sqrt(size.Number)) } // covar is an implementation of the formula functions COVAR, COVARIANCE.P and // COVARIANCE.S. func (fn *formulaFuncs) covar(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) } array1 := argsList.Front().Value.(formulaArg) array2 := argsList.Back().Value.(formulaArg) left, right := array1.ToList(), array2.ToList() n := len(left) if n != len(right) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } l1, l2 := list.New(), list.New() l1.PushBack(array1) l2.PushBack(array2) result, skip := 0.0, 0 mean1, mean2 := fn.AVERAGE(l1), fn.AVERAGE(l2) for i := 0; i < n; i++ { arg1 := left[i].ToNumber() arg2 := right[i].ToNumber() if arg1.Type == ArgError || arg2.Type == ArgError { skip++ continue } result += (arg1.Number - mean1.Number) * (arg2.Number - mean2.Number) } if name == "COVARIANCE.S" { return newNumberFormulaArg(result / float64(n-skip-1)) } return newNumberFormulaArg(result / float64(n-skip)) } // COVAR function calculates the covariance of two supplied sets of values. The // syntax of the function is: // // COVAR(array1,array2) func (fn *formulaFuncs) COVAR(argsList *list.List) formulaArg { return fn.covar("COVAR", argsList) } // COVARIANCEdotP function calculates the population covariance of two supplied // sets of values. The syntax of the function is: // // COVARIANCE.P(array1,array2) func (fn *formulaFuncs) COVARIANCEdotP(argsList *list.List) formulaArg { return fn.covar("COVARIANCE.P", argsList) } // COVARIANCEdotS function calculates the sample covariance of two supplied // sets of values. The syntax of the function is: // // COVARIANCE.S(array1,array2) func (fn *formulaFuncs) COVARIANCEdotS(argsList *list.List) formulaArg { return fn.covar("COVARIANCE.S", argsList) } // calcStringCountSum is part of the implementation countSum. func calcStringCountSum(countText bool, count, sum float64, num, arg formulaArg) (float64, float64) { if countText && num.Type == ArgError && arg.String != "" { count++ } if num.Type == ArgNumber { sum += num.Number count++ } return count, sum } // countSum get count and sum for a formula arguments array. func (fn *formulaFuncs) countSum(countText bool, args []formulaArg) (count, sum float64) { for _, arg := range args { switch arg.Type { case ArgNumber: if countText || !arg.Boolean { sum += arg.Number count++ } case ArgString: if !countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { continue } else if countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { num := arg.ToBool() if num.Type == ArgNumber { count++ sum += num.Number continue } } num := arg.ToNumber() count, sum = calcStringCountSum(countText, count, sum, num, arg) case ArgList, ArgMatrix: cnt, summary := fn.countSum(countText, arg.ToList()) sum += summary count += cnt } } return } // CORREL function calculates the Pearson Product-Moment Correlation // Coefficient for two sets of values. The syntax of the function is: // // CORREL(array1,array2) func (fn *formulaFuncs) CORREL(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "CORREL requires 2 arguments") } array1 := argsList.Front().Value.(formulaArg) array2 := argsList.Back().Value.(formulaArg) left, right := array1.ToList(), array2.ToList() n := len(left) if n != len(right) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } l1, l2, l3 := list.New(), list.New(), list.New() for i := 0; i < n; i++ { if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 { l1.PushBack(lhs) l2.PushBack(rhs) } } stdev1, stdev2 := fn.STDEV(l1), fn.STDEV(l2) if stdev1.Number == 0 || stdev2.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } mean1, mean2, skip := fn.AVERAGE(l1), fn.AVERAGE(l2), 0 for i := 0; i < n; i++ { lhs, rhs := left[i].ToNumber(), right[i].ToNumber() if lhs.Number == 0 || rhs.Number == 0 { skip++ continue } l3.PushBack(newNumberFormulaArg((lhs.Number - mean1.Number) * (rhs.Number - mean2.Number))) } return newNumberFormulaArg(fn.SUM(l3).Number / float64(n-skip-1) / stdev1.Number / stdev2.Number) } // COUNT function returns the count of numeric values in a supplied set of // cells or values. This count includes both numbers and dates. The syntax of // the function is: // // COUNT(value1,[value2],...) func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg { var count int for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: if num := arg.ToNumber(); num.Type == ArgNumber { count++ } case ArgNumber: count++ case ArgMatrix: for _, row := range arg.Matrix { for _, cell := range row { if cell.Type == ArgNumber { count++ } } } } } return newNumberFormulaArg(float64(count)) } // COUNTA function returns the number of non-blanks within a supplied set of // cells or values. The syntax of the function is: // // COUNTA(value1,[value2],...) func (fn *formulaFuncs) COUNTA(argsList *list.List) formulaArg { var count int for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: if arg.String != "" { count++ } case ArgNumber: count++ case ArgMatrix: for _, row := range arg.ToList() { switch row.Type { case ArgString: if row.String != "" { count++ } case ArgNumber: count++ } } } } return newNumberFormulaArg(float64(count)) } // COUNTBLANK function returns the number of blank cells in a supplied range. // The syntax of the function is: // // COUNTBLANK(range) func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COUNTBLANK requires 1 argument") } var count float64 for _, cell := range argsList.Front().Value.(formulaArg).ToList() { if cell.Type == ArgEmpty { count++ } } return newNumberFormulaArg(count) } // COUNTIF function returns the number of cells within a supplied range, that // satisfy a given criteria. The syntax of the function is: // // COUNTIF(range,criteria) func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "COUNTIF requires 2 arguments") } var ( criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg)) count float64 ) for _, cell := range argsList.Front().Value.(formulaArg).ToList() { if cell.Type == ArgString && criteria.Condition.Type != ArgString { continue } if ok, _ := formulaCriteriaEval(cell, criteria); ok { count++ } } return newNumberFormulaArg(count) } // formulaIfsMatch function returns cells reference array which match criteria. func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) { for i := 0; i < len(args)-1; i += 2 { var match []cellRef matrix, criteria := args[i].Matrix, formulaCriteriaParser(args[i+1]) if i == 0 { for rowIdx, row := range matrix { for colIdx, col := range row { if ok, _ := formulaCriteriaEval(col, criteria); ok { match = append(match, cellRef{Col: colIdx, Row: rowIdx}) } } } } else { match = []cellRef{} for _, ref := range cellRefs { value := matrix[ref.Row][ref.Col] if ok, _ := formulaCriteriaEval(value, criteria); ok { match = append(match, ref) } } } cellRefs = match[:] } return } // COUNTIFS function returns the number of rows within a table, that satisfy a // set of given criteria. The syntax of the function is: // // COUNTIFS(criteria_range1,criteria1,[criteria_range2,criteria2],...) func (fn *formulaFuncs) COUNTIFS(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "COUNTIFS requires at least 2 arguments") } if argsList.Len()%2 != 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var args []formulaArg for arg := argsList.Front(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } return newNumberFormulaArg(float64(len(formulaIfsMatch(args)))) } // CRITBINOM function returns the inverse of the Cumulative Binomial // Distribution. I.e. for a specific number of independent trials, the // function returns the smallest value (number of successes) for which the // cumulative binomial distribution is greater than or equal to a specified // value. The syntax of the function is: // // CRITBINOM(trials,probability_s,alpha) func (fn *formulaFuncs) CRITBINOM(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "CRITBINOM requires 3 numeric arguments") } return fn.BINOMdotINV(argsList) } // DEVSQ function calculates the sum of the squared deviations from the sample // mean. The syntax of the function is: // // DEVSQ(number1,[number2],...) func (fn *formulaFuncs) DEVSQ(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "DEVSQ requires at least 1 numeric argument") } avg, count, result := fn.AVERAGE(argsList), -1, 0.0 for arg := argsList.Front(); arg != nil; arg = arg.Next() { for _, cell := range arg.Value.(formulaArg).ToList() { if cell.Type != ArgNumber { continue } count++ if count == 0 { result = math.Pow(cell.Number-avg.Number, 2) continue } result += math.Pow(cell.Number-avg.Number, 2) } } if count == -1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(result) } // FISHER function calculates the Fisher Transformation for a supplied value. // The syntax of the function is: // // FISHER(x) func (fn *formulaFuncs) FISHER(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgString: arg := token.ToNumber() if arg.Type == ArgNumber { if arg.Number <= -1 || arg.Number >= 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(0.5 * math.Log((1+arg.Number)/(1-arg.Number))) } case ArgNumber: if token.Number <= -1 || token.Number >= 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(0.5 * math.Log((1+token.Number)/(1-token.Number))) } return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument") } // FISHERINV function calculates the inverse of the Fisher Transformation and // returns a value between -1 and +1. The syntax of the function is: // // FISHERINV(y) func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgString: arg := token.ToNumber() if arg.Type == ArgNumber { return newNumberFormulaArg((math.Exp(2*arg.Number) - 1) / (math.Exp(2*arg.Number) + 1)) } case ArgNumber: return newNumberFormulaArg((math.Exp(2*token.Number) - 1) / (math.Exp(2*token.Number) + 1)) } return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument") } // FORECAST function predicts a future point on a linear trend line fitted to a // supplied set of x- and y- values. The syntax of the function is: // // FORECAST(x,known_y's,known_x's) func (fn *formulaFuncs) FORECAST(argsList *list.List) formulaArg { return fn.pearsonProduct("FORECAST", 3, argsList) } // FORECASTdotLINEAR function predicts a future point on a linear trend line // fitted to a supplied set of x- and y- values. The syntax of the function is: // // FORECAST.LINEAR(x,known_y's,known_x's) func (fn *formulaFuncs) FORECASTdotLINEAR(argsList *list.List) formulaArg { return fn.pearsonProduct("FORECAST.LINEAR", 3, argsList) } // matrixToSortedColumnList convert matrix formula arguments to a ascending // order list by column. func matrixToSortedColumnList(arg formulaArg) formulaArg { var ( mtx []formulaArg cols = len(arg.Matrix[0]) ) for colIdx := 0; colIdx < cols; colIdx++ { for _, row := range arg.Matrix { cell := row[colIdx] if cell.Type == ArgError { return cell } if cell.Type == ArgNumber { mtx = append(mtx, cell) } } } argsList := newListFormulaArg(mtx) sort.Slice(argsList.List, func(i, j int) bool { return argsList.List[i].Number < argsList.List[j].Number }) return argsList } // FREQUENCY function to count how many children fall into different age // ranges. The syntax of the function is: // // FREQUENCY(data_array,bins_array) func (fn *formulaFuncs) FREQUENCY(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "FREQUENCY requires 2 arguments") } data, bins := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg) if len(data.Matrix) == 0 { data.Matrix = [][]formulaArg{{data}} } if len(bins.Matrix) == 0 { bins.Matrix = [][]formulaArg{{bins}} } var ( dataMtx, binsMtx formulaArg c [][]formulaArg i, j int ) if dataMtx = matrixToSortedColumnList(data); dataMtx.Type != ArgList { return dataMtx } if binsMtx = matrixToSortedColumnList(bins); binsMtx.Type != ArgList { return binsMtx } for row := 0; row < len(binsMtx.List)+1; row++ { var rows []formulaArg for col := 0; col < 1; col++ { rows = append(rows, newNumberFormulaArg(0)) } c = append(c, rows) } for j = 0; j < len(binsMtx.List); j++ { n := 0.0 for i < len(dataMtx.List) && dataMtx.List[i].Number <= binsMtx.List[j].Number { n++ i++ } c[j] = []formulaArg{newNumberFormulaArg(n)} } c[j] = []formulaArg{newNumberFormulaArg(float64(len(dataMtx.List) - i))} if len(c) > 2 { c[1], c[2] = c[2], c[1] } return newMatrixFormulaArg(c) } // GAMMA function returns the value of the Gamma Function, Γ(n), for a // specified number, n. The syntax of the function is: // // GAMMA(number) func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") } if number.Number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(math.Gamma(number.Number)) } // GAMMAdotDIST function returns the Gamma Distribution, which is frequently // used to provide probabilities for values that may have a skewed // distribution, such as queuing analysis. // // GAMMA.DIST(x,alpha,beta,cumulative) func (fn *formulaFuncs) GAMMAdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.DIST requires 4 arguments") } return fn.GAMMADIST(argsList) } // GAMMADIST function returns the Gamma Distribution, which is frequently used // to provide probabilities for values that may have a skewed distribution, // such as queuing analysis. // // GAMMADIST(x,alpha,beta,cumulative) func (fn *formulaFuncs) GAMMADIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMADIST requires 4 arguments") } var x, alpha, beta, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if x.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { return alpha } if beta = argsList.Back().Prev().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber { return beta } if alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if cumulative.Number == 1 { return newNumberFormulaArg(incompleteGamma(alpha.Number, x.Number/beta.Number) / math.Gamma(alpha.Number)) } return newNumberFormulaArg((1 / (math.Pow(beta.Number, alpha.Number) * math.Gamma(alpha.Number))) * math.Pow(x.Number, alpha.Number-1) * math.Exp(0-(x.Number/beta.Number))) } // gammainv returns the inverse of the Gamma distribution for the specified // value. func gammainv(probability, alpha, beta float64) float64 { xLo, xHi := 0.0, alpha*beta*5 dx, x, xNew, result := 1024.0, 1.0, 1.0, 0.0 for i := 0; math.Abs(dx) > 8.88e-016 && i <= 256; i++ { result = incompleteGamma(alpha, x/beta) / math.Gamma(alpha) e := result - probability if e == 0 { dx = 0 } else if e < 0 { xLo = x } else { xHi = x } pdf := (1 / (math.Pow(beta, alpha) * math.Gamma(alpha))) * math.Pow(x, alpha-1) * math.Exp(0-(x/beta)) if pdf != 0 { dx = e / pdf xNew = x - dx } if xNew < xLo || xNew > xHi || pdf == 0 { xNew = (xLo + xHi) / 2 dx = xNew - x } x = xNew } return x } // GAMMAdotINV function returns the inverse of the Gamma Cumulative // Distribution. The syntax of the function is: // // GAMMA.INV(probability,alpha,beta) func (fn *formulaFuncs) GAMMAdotINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.INV requires 3 arguments") } return fn.GAMMAINV(argsList) } // GAMMAINV function returns the inverse of the Gamma Cumulative Distribution. // The syntax of the function is: // // GAMMAINV(probability,alpha,beta) func (fn *formulaFuncs) GAMMAINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMAINV requires 3 arguments") } var probability, alpha, beta formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if probability.Number < 0 || probability.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { return alpha } if beta = argsList.Back().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber { return beta } if alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(gammainv(probability.Number, alpha.Number, beta.Number)) } // GAMMALN function returns the natural logarithm of the Gamma Function, Γ // (n). The syntax of the function is: // // GAMMALN(x) func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") } if x.Number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(math.Log(math.Gamma(x.Number))) } // GAMMALNdotPRECISE function returns the natural logarithm of the Gamma // Function, Γ(n). The syntax of the function is: // // GAMMALN.PRECISE(x) func (fn *formulaFuncs) GAMMALNdotPRECISE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN.PRECISE requires 1 numeric argument") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } if x.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(getLogGamma(x.Number)) } // GAUSS function returns the probability that a member of a standard normal // population will fall between the mean and a specified number of standard // deviations from the mean. The syntax of the function is: // // GAUSS(z) func (fn *formulaFuncs) GAUSS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAUSS requires 1 numeric argument") } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) args.PushBack(newBoolFormulaArg(true)) normdist := fn.NORMDIST(args) if normdist.Type != ArgNumber { return normdist } return newNumberFormulaArg(normdist.Number - 0.5) } // GEOMEAN function calculates the geometric mean of a supplied set of values. // The syntax of the function is: // // GEOMEAN(number1,[number2],...) func (fn *formulaFuncs) GEOMEAN(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "GEOMEAN requires at least 1 numeric argument") } product := fn.PRODUCT(argsList) if product.Type != ArgNumber { return product } count := fn.COUNT(argsList) minVal := fn.MIN(argsList) if product.Number > 0 && minVal.Number > 0 { return newNumberFormulaArg(math.Pow(product.Number, 1/count.Number)) } return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } // getNewMatrix create matrix by given columns and rows. func getNewMatrix(c, r int) (matrix [][]float64) { for i := 0; i < c; i++ { for j := 0; j < r; j++ { for x := len(matrix); x <= i; x++ { matrix = append(matrix, []float64{}) } for y := len(matrix[i]); y <= j; y++ { matrix[i] = append(matrix[i], 0) } matrix[i][j] = 0 } } return } // approxSub subtract two values, if signs are identical and the values are // equal, will be returns 0 instead of calculating the subtraction. func approxSub(a, b float64) float64 { if ((a < 0 && b < 0) || (a > 0 && b > 0)) && math.Abs(a-b) < 2.22045e-016 { return 0 } return a - b } // matrixClone return a copy of all elements of the original matrix. func matrixClone(matrix [][]float64) (cloneMatrix [][]float64) { for i := 0; i < len(matrix); i++ { for j := 0; j < len(matrix[i]); j++ { for x := len(cloneMatrix); x <= i; x++ { cloneMatrix = append(cloneMatrix, []float64{}) } for k := len(cloneMatrix[i]); k <= j; k++ { cloneMatrix[i] = append(cloneMatrix[i], 0) } cloneMatrix[i][j] = matrix[i][j] } } return } // trendGrowthMatrixInfo defined matrix checking result. type trendGrowthMatrixInfo struct { trendType, nCX, nCY, nRX, nRY, M, N int mtxX, mtxY [][]float64 } // prepareTrendGrowthMtxX is a part of implementation of the trend growth prepare. func prepareTrendGrowthMtxX(mtxX [][]float64) [][]float64 { var mtx [][]float64 for i := 0; i < len(mtxX); i++ { for j := 0; j < len(mtxX[i]); j++ { if mtxX[i][j] == 0 { return nil } for x := len(mtx); x <= j; x++ { mtx = append(mtx, []float64{}) } for y := len(mtx[j]); y <= i; y++ { mtx[j] = append(mtx[j], 0) } mtx[j][i] = mtxX[i][j] } } return mtx } // prepareTrendGrowthMtxY is a part of implementation of the trend growth prepare. func prepareTrendGrowthMtxY(bLOG bool, mtxY [][]float64) [][]float64 { var mtx [][]float64 for i := 0; i < len(mtxY); i++ { for j := 0; j < len(mtxY[i]); j++ { if mtxY[i][j] == 0 { return nil } for x := len(mtx); x <= j; x++ { mtx = append(mtx, []float64{}) } for y := len(mtx[j]); y <= i; y++ { mtx[j] = append(mtx[j], 0) } mtx[j][i] = mtxY[i][j] } } if bLOG { var pNewY [][]float64 for i := 0; i < len(mtxY); i++ { for j := 0; j < len(mtxY[i]); j++ { fVal := mtxY[i][j] if fVal <= 0 { return nil } for x := len(pNewY); x <= j; x++ { pNewY = append(pNewY, []float64{}) } for y := len(pNewY[j]); y <= i; y++ { pNewY[j] = append(pNewY[j], 0) } pNewY[j][i] = math.Log(fVal) } } mtx = pNewY } return mtx } // prepareTrendGrowth check and return the result. func prepareTrendGrowth(bLOG bool, mtxX, mtxY [][]float64) (*trendGrowthMatrixInfo, formulaArg) { var nCX, nRX, M, N, trendType int nRY, nCY := len(mtxY), len(mtxY[0]) cntY := nCY * nRY newY := prepareTrendGrowthMtxY(bLOG, mtxY) if newY == nil { return nil, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } var newX [][]float64 if len(mtxX) != 0 { nRX, nCX = len(mtxX), len(mtxX[0]) if newX = prepareTrendGrowthMtxX(mtxX); newX == nil { return nil, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if nCX == nCY && nRX == nRY { trendType, M, N = 1, 1, cntY // simple regression } else if nCY != 1 && nRY != 1 { return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } else if nCY == 1 { if nRX != nRY { return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } trendType, M, N = 2, nCX, nRY } else if nCX != nCY { return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } else { trendType, M, N = 3, nRX, nCY } } else { newX = getNewMatrix(nCY, nRY) nCX, nRX = nCY, nRY num := 1.0 for i := 0; i < nRY; i++ { for j := 0; j < nCY; j++ { newX[j][i] = num num++ } } trendType, M, N = 1, 1, cntY } return &trendGrowthMatrixInfo{ trendType: trendType, nCX: nCX, nCY: nCY, nRX: nRX, nRY: nRY, M: M, N: N, mtxX: newX, mtxY: newY, }, newEmptyFormulaArg() } // calcPosition calculate position for matrix by given index. func calcPosition(mtx [][]float64, idx int) (row, col int) { rowSize := len(mtx[0]) col = idx if rowSize > 1 { col = idx / rowSize } row = idx - col*rowSize return } // getDouble returns float64 data type value in the matrix by given index. func getDouble(mtx [][]float64, idx int) float64 { row, col := calcPosition(mtx, idx) return mtx[col][row] } // putDouble set a float64 data type value in the matrix by given index. func putDouble(mtx [][]float64, idx int, val float64) { row, col := calcPosition(mtx, idx) mtx[col][row] = val } // calcMeanOverAll returns mean of the given matrix by over all element. func calcMeanOverAll(mtx [][]float64, n int) float64 { var sum float64 for i := 0; i < len(mtx); i++ { for j := 0; j < len(mtx[i]); j++ { sum += mtx[i][j] } } return sum / float64(n) } // calcSumProduct returns uses the matrices as vectors of length M over all // element. func calcSumProduct(mtxA, mtxB [][]float64, m int) float64 { sum := 0.0 for i := 0; i < m; i++ { sum += getDouble(mtxA, i) * getDouble(mtxB, i) } return sum } // calcColumnMeans calculates means of the columns of matrix. func calcColumnMeans(mtxX, mtxRes [][]float64, c, r int) { for i := 0; i < c; i++ { var sum float64 for k := 0; k < r; k++ { sum += mtxX[i][k] } putDouble(mtxRes, i, sum/float64(r)) } } // calcColumnsDelta calculates subtract of the columns of matrix. func calcColumnsDelta(mtx, columnMeans [][]float64, c, r int) { for i := 0; i < c; i++ { for k := 0; k < r; k++ { mtx[i][k] = approxSub(mtx[i][k], getDouble(columnMeans, i)) } } } // calcSign returns sign by given value, no mathematical signum, but used to // switch between adding and subtracting. func calcSign(val float64) float64 { if val > 0 { return 1 } return -1 } // calcColsMaximumNorm is a special version for use within QR // decomposition. Maximum norm of column index c starting in row index r; // matrix A has count n rows. func calcColsMaximumNorm(mtxA [][]float64, c, r, n int) float64 { var norm float64 for row := r; row < n; row++ { if norm < math.Abs(mtxA[c][row]) { norm = math.Abs(mtxA[c][row]) } } return norm } // calcFastMult returns multiply n x m matrix A with m x l matrix B to n x l matrix R. func calcFastMult(mtxA, mtxB, mtxR [][]float64, n, m, l int) { var sum float64 for row := 0; row < n; row++ { for col := 0; col < l; col++ { sum = 0.0 for k := 0; k < m; k++ { sum += mtxA[k][row] * mtxB[col][k] } mtxR[col][row] = sum } } } // calcRowsEuclideanNorm is a special version for use within QR // decomposition. Euclidean norm of column index c starting in row index r; // matrix a has count n rows. func calcRowsEuclideanNorm(mtxA [][]float64, c, r, n int) float64 { var norm float64 for row := r; row < n; row++ { norm += mtxA[c][row] * mtxA[c][row] } return math.Sqrt(norm) } // calcRowsSumProduct is a special version for use within QR decomposition. // starting in row index r; // a and b are indices of columns, matrices A and B have count n rows. func calcRowsSumProduct(mtxA [][]float64, a int, mtxB [][]float64, b, r, n int) float64 { var result float64 for row := r; row < n; row++ { result += mtxA[a][row] * mtxB[b][row] } return result } // calcSolveWithUpperRightTriangle solve for X in R*X=S using back substitution. func calcSolveWithUpperRightTriangle(mtxA [][]float64, vecR []float64, mtxS [][]float64, k int, bIsTransposed bool) { var row int for rowp1 := k; rowp1 > 0; rowp1-- { row = rowp1 - 1 sum := getDouble(mtxS, row) for col := rowp1; col < k; col++ { if bIsTransposed { sum -= mtxA[row][col] * getDouble(mtxS, col) } else { sum -= mtxA[col][row] * getDouble(mtxS, col) } } putDouble(mtxS, row, sum/vecR[row]) } } // calcRowQRDecomposition calculates a QR decomposition with Householder // reflection. func calcRowQRDecomposition(mtxA [][]float64, vecR []float64, k, n int) bool { for col := 0; col < k; col++ { scale := calcColsMaximumNorm(mtxA, col, col, n) if scale == 0 { return false } for row := col; row < n; row++ { mtxA[col][row] = mtxA[col][row] / scale } euclid := calcRowsEuclideanNorm(mtxA, col, col, n) factor := 1.0 / euclid / (euclid + math.Abs(mtxA[col][col])) signum := calcSign(mtxA[col][col]) mtxA[col][col] = mtxA[col][col] + signum*euclid vecR[col] = -signum * scale * euclid // apply Householder transformation to A for c := col + 1; c < k; c++ { sum := calcRowsSumProduct(mtxA, col, mtxA, c, col, n) for row := col; row < n; row++ { mtxA[c][row] = mtxA[c][row] - sum*factor*mtxA[col][row] } } } return true } // calcApplyColsHouseholderTransformation transposed matrices A and Y. func calcApplyColsHouseholderTransformation(mtxA [][]float64, r int, mtxY [][]float64, n int) { denominator := calcColsSumProduct(mtxA, r, mtxA, r, r, n) numerator := calcColsSumProduct(mtxA, r, mtxY, 0, r, n) factor := 2 * (numerator / denominator) for col := r; col < n; col++ { putDouble(mtxY, col, getDouble(mtxY, col)-factor*mtxA[col][r]) } } // calcRowMeans calculates means of the rows of matrix. func calcRowMeans(mtxX, mtxRes [][]float64, c, r int) { for k := 0; k < r; k++ { var fSum float64 for i := 0; i < c; i++ { fSum += mtxX[i][k] } mtxRes[k][0] = fSum / float64(c) } } // calcRowsDelta calculates subtract of the rows of matrix. func calcRowsDelta(mtx, rowMeans [][]float64, c, r int) { for k := 0; k < r; k++ { for i := 0; i < c; i++ { mtx[i][k] = approxSub(mtx[i][k], rowMeans[k][0]) } } } // calcColumnMaximumNorm returns maximum norm of row index R starting in col // index C; matrix A has count N columns. func calcColumnMaximumNorm(mtxA [][]float64, r, c, n int) float64 { var norm float64 for col := c; col < n; col++ { if norm < math.Abs(mtxA[col][r]) { norm = math.Abs(mtxA[col][r]) } } return norm } // calcColsEuclideanNorm returns euclidean norm of row index R starting in // column index C; matrix A has count N columns. func calcColsEuclideanNorm(mtxA [][]float64, r, c, n int) float64 { var norm float64 for col := c; col < n; col++ { norm += (mtxA[col][r]) * (mtxA[col][r]) } return math.Sqrt(norm) } // calcColsSumProduct returns sum product for given matrix. func calcColsSumProduct(mtxA [][]float64, a int, mtxB [][]float64, b, c, n int) float64 { var result float64 for col := c; col < n; col++ { result += mtxA[col][a] * mtxB[col][b] } return result } // calcColQRDecomposition same with transposed matrix A, N is count of // columns, k count of rows. func calcColQRDecomposition(mtxA [][]float64, vecR []float64, k, n int) bool { var sum float64 for row := 0; row < k; row++ { // calculate vector u of the householder transformation scale := calcColumnMaximumNorm(mtxA, row, row, n) if scale == 0 { return false } for col := row; col < n; col++ { mtxA[col][row] = mtxA[col][row] / scale } euclid := calcColsEuclideanNorm(mtxA, row, row, n) factor := 1 / euclid / (euclid + math.Abs(mtxA[row][row])) signum := calcSign(mtxA[row][row]) mtxA[row][row] = mtxA[row][row] + signum*euclid vecR[row] = -signum * scale * euclid // apply Householder transformation to A for r := row + 1; r < k; r++ { sum = calcColsSumProduct(mtxA, row, mtxA, r, row, n) for col := row; col < n; col++ { mtxA[col][r] = mtxA[col][r] - sum*factor*mtxA[col][row] } } } return true } // calcApplyRowsHouseholderTransformation applies a Householder transformation to a // column vector Y with is given as Nx1 Matrix. The vector u, from which the // Householder transformation is built, is the column part in matrix A, with // column index c, starting with row index c. A is the result of the QR // decomposition as obtained from calcRowQRDecomposition. func calcApplyRowsHouseholderTransformation(mtxA [][]float64, c int, mtxY [][]float64, n int) { denominator := calcRowsSumProduct(mtxA, c, mtxA, c, c, n) numerator := calcRowsSumProduct(mtxA, c, mtxY, 0, c, n) factor := 2 * (numerator / denominator) for row := c; row < n; row++ { putDouble(mtxY, row, getDouble(mtxY, row)-factor*mtxA[c][row]) } } // calcTrendGrowthSimpleRegression calculate simple regression for the calcTrendGrowth. func calcTrendGrowthSimpleRegression(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, N int) { var meanX float64 if bConstant { meanX = calcMeanOverAll(mtxX, N) for i := 0; i < len(mtxX); i++ { for j := 0; j < len(mtxX[i]); j++ { mtxX[i][j] = approxSub(mtxX[i][j], meanX) } } } sumXY := calcSumProduct(mtxX, mtxY, N) sumX2 := calcSumProduct(mtxX, mtxX, N) slope := sumXY / sumX2 var help float64 var intercept float64 if bConstant { intercept = meanY - slope*meanX for i := 0; i < len(mtxRes); i++ { for j := 0; j < len(mtxRes[i]); j++ { help = newX[i][j]*slope + intercept if bGrowth { mtxRes[i][j] = math.Exp(help) } else { mtxRes[i][j] = help } } } } else { for i := 0; i < len(mtxRes); i++ { for j := 0; j < len(mtxRes[i]); j++ { help = newX[i][j] * slope if bGrowth { mtxRes[i][j] = math.Exp(help) } else { mtxRes[i][j] = help } } } } } // calcTrendGrowthMultipleRegressionPart1 calculate multiple regression for the // calcTrendGrowth. func calcTrendGrowthMultipleRegressionPart1(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, RXN, K, N int) { vecR := make([]float64, N) // for QR decomposition means := getNewMatrix(K, 1) // mean of each column slopes := getNewMatrix(1, K) // from b1 to bK if len(means) == 0 || len(slopes) == 0 { return } if bConstant { calcColumnMeans(mtxX, means, K, N) calcColumnsDelta(mtxX, means, K, N) } if !calcRowQRDecomposition(mtxX, vecR, K, N) { return } // Later on we will divide by elements of vecR, so make sure that they aren't zero. bIsSingular := false for row := 0; row < K && !bIsSingular; row++ { bIsSingular = bIsSingular || vecR[row] == 0 } if bIsSingular { return } for col := 0; col < K; col++ { calcApplyRowsHouseholderTransformation(mtxX, col, mtxY, N) } for col := 0; col < K; col++ { putDouble(slopes, col, getDouble(mtxY, col)) } calcSolveWithUpperRightTriangle(mtxX, vecR, slopes, K, false) // Fill result matrix calcFastMult(newX, slopes, mtxRes, RXN, K, 1) if bConstant { intercept := meanY - calcSumProduct(means, slopes, K) for row := 0; row < RXN; row++ { mtxRes[0][row] = mtxRes[0][row] + intercept } } if bGrowth { for i := 0; i < RXN; i++ { putDouble(mtxRes, i, math.Exp(getDouble(mtxRes, i))) } } } // calcTrendGrowthMultipleRegressionPart2 calculate multiple regression for the // calcTrendGrowth. func calcTrendGrowthMultipleRegressionPart2(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, nCXN, K, N int) { vecR := make([]float64, N) // for QR decomposition means := getNewMatrix(K, 1) // mean of each row slopes := getNewMatrix(K, 1) // row from b1 to bK if len(means) == 0 || len(slopes) == 0 { return } if bConstant { calcRowMeans(mtxX, means, N, K) calcRowsDelta(mtxX, means, N, K) } if !calcColQRDecomposition(mtxX, vecR, K, N) { return } // later on we will divide by elements of vecR, so make sure that they aren't zero bIsSingular := false for row := 0; row < K && !bIsSingular; row++ { bIsSingular = bIsSingular || vecR[row] == 0 } if bIsSingular { return } for row := 0; row < K; row++ { calcApplyColsHouseholderTransformation(mtxX, row, mtxY, N) } for col := 0; col < K; col++ { putDouble(slopes, col, getDouble(mtxY, col)) } calcSolveWithUpperRightTriangle(mtxX, vecR, slopes, K, true) // fill result matrix calcFastMult(slopes, newX, mtxRes, 1, K, nCXN) if bConstant { fIntercept := meanY - calcSumProduct(means, slopes, K) for col := 0; col < nCXN; col++ { mtxRes[col][0] = mtxRes[col][0] + fIntercept } } if bGrowth { for i := 0; i < nCXN; i++ { putDouble(mtxRes, i, math.Exp(getDouble(mtxRes, i))) } } } // calcTrendGrowthRegression is a part of implementation of the calcTrendGrowth. func calcTrendGrowthRegression(bConstant, bGrowth bool, trendType, nCXN, nRXN, K, N int, mtxY, mtxX, newX, mtxRes [][]float64) { if len(mtxRes) == 0 { return } var meanY float64 if bConstant { copyX, copyY := matrixClone(mtxX), matrixClone(mtxY) mtxX, mtxY = copyX, copyY meanY = calcMeanOverAll(mtxY, N) for i := 0; i < len(mtxY); i++ { for j := 0; j < len(mtxY[i]); j++ { mtxY[i][j] = approxSub(mtxY[i][j], meanY) } } } switch trendType { case 1: calcTrendGrowthSimpleRegression(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, N) case 2: calcTrendGrowthMultipleRegressionPart1(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, nRXN, K, N) default: calcTrendGrowthMultipleRegressionPart2(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, nCXN, K, N) } } // calcTrendGrowth returns values along a predicted exponential trend. func calcTrendGrowth(mtxY, mtxX, newX [][]float64, bConstant, bGrowth bool) ([][]float64, formulaArg) { getMatrixParams, errArg := prepareTrendGrowth(bGrowth, mtxX, mtxY) if errArg.Type != ArgEmpty { return nil, errArg } trendType := getMatrixParams.trendType nCX := getMatrixParams.nCX nRX := getMatrixParams.nRX K := getMatrixParams.M N := getMatrixParams.N mtxX = getMatrixParams.mtxX mtxY = getMatrixParams.mtxY // checking if data samples are enough if (bConstant && (N < K+1)) || (!bConstant && (N < K)) || (N < 1) || (K < 1) { return nil, errArg } // set the default newX if necessary nCXN, nRXN := nCX, nRX if len(newX) == 0 { newX = matrixClone(mtxX) // mtxX will be changed to X-meanX } else { nRXN, nCXN = len(newX[0]), len(newX) if (trendType == 2 && K != nCXN) || (trendType == 3 && K != nRXN) { return nil, errArg } } var mtxRes [][]float64 switch trendType { case 1: mtxRes = getNewMatrix(nCXN, nRXN) case 2: mtxRes = getNewMatrix(1, nRXN) default: mtxRes = getNewMatrix(nCXN, 1) } calcTrendGrowthRegression(bConstant, bGrowth, trendType, nCXN, nRXN, K, N, mtxY, mtxX, newX, mtxRes) return mtxRes, errArg } // trendGrowth is an implementation of the formula functions GROWTH and TREND. func (fn *formulaFuncs) trendGrowth(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } if argsList.Len() > 4 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 4 arguments", name)) } var knowY, knowX, newX [][]float64 var errArg formulaArg constArg := newBoolFormulaArg(true) knowY, errArg = newNumberMatrix(argsList.Front().Value.(formulaArg), false) if errArg.Type == ArgError { return errArg } if argsList.Len() > 1 { knowX, errArg = newNumberMatrix(argsList.Front().Next().Value.(formulaArg), false) if errArg.Type == ArgError { return errArg } } if argsList.Len() > 2 { newX, errArg = newNumberMatrix(argsList.Front().Next().Next().Value.(formulaArg), false) if errArg.Type == ArgError { return errArg } } if argsList.Len() > 3 { if constArg = argsList.Back().Value.(formulaArg).ToBool(); constArg.Type != ArgNumber { return constArg } } var mtxNewX [][]float64 for i := 0; i < len(newX); i++ { for j := 0; j < len(newX[i]); j++ { for x := len(mtxNewX); x <= j; x++ { mtxNewX = append(mtxNewX, []float64{}) } for k := len(mtxNewX[j]); k <= i; k++ { mtxNewX[j] = append(mtxNewX[j], 0) } mtxNewX[j][i] = newX[i][j] } } mtx, errArg := calcTrendGrowth(knowY, knowX, mtxNewX, constArg.Number == 1, name == "GROWTH") if errArg.Type != ArgEmpty { return errArg } return newMatrixFormulaArg(newFormulaArgMatrix(mtx)) } // GROWTH function calculates the exponential growth curve through a given set // of y-values and (optionally), one or more sets of x-values. The function // then extends the curve to calculate additional y-values for a further // supplied set of new x-values. The syntax of the function is: // // GROWTH(known_y's,[known_x's],[new_x's],[const]) func (fn *formulaFuncs) GROWTH(argsList *list.List) formulaArg { return fn.trendGrowth("GROWTH", argsList) } // HARMEAN function calculates the harmonic mean of a supplied set of values. // The syntax of the function is: // // HARMEAN(number1,[number2],...) func (fn *formulaFuncs) HARMEAN(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "HARMEAN requires at least 1 argument") } if minVal := fn.MIN(argsList); minVal.Number < 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } number, val, cnt := 0.0, 0.0, 0.0 for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: num := arg.ToNumber() if num.Type != ArgNumber { continue } number = num.Number case ArgNumber: number = arg.Number } if number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } val += 1 / number cnt++ } return newNumberFormulaArg(1 / (val / cnt)) } // checkHYPGEOMDISTArgs checking arguments for the formula function HYPGEOMDIST // and HYPGEOM.DIST. func checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop formulaArg) bool { return sampleS.Number < 0 || sampleS.Number > math.Min(numberSample.Number, populationS.Number) || sampleS.Number < math.Max(0, numberSample.Number-numberPop.Number+populationS.Number) || numberSample.Number <= 0 || numberSample.Number > numberPop.Number || populationS.Number <= 0 || populationS.Number > numberPop.Number || numberPop.Number <= 0 } // prepareHYPGEOMDISTArgs prepare arguments for the formula function // HYPGEOMDIST and HYPGEOM.DIST. func (fn *formulaFuncs) prepareHYPGEOMDISTArgs(name string, argsList *list.List) formulaArg { if name == "HYPGEOMDIST" && argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOMDIST requires 4 numeric arguments") } if name == "HYPGEOM.DIST" && argsList.Len() != 5 { return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOM.DIST requires 5 arguments") } var sampleS, numberSample, populationS, numberPop, cumulative formulaArg if sampleS = argsList.Front().Value.(formulaArg).ToNumber(); sampleS.Type != ArgNumber { return sampleS } if numberSample = argsList.Front().Next().Value.(formulaArg).ToNumber(); numberSample.Type != ArgNumber { return numberSample } if populationS = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); populationS.Type != ArgNumber { return populationS } if numberPop = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); numberPop.Type != ArgNumber { return numberPop } if checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if name == "HYPGEOM.DIST" { if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { return cumulative } } return newListFormulaArg([]formulaArg{sampleS, numberSample, populationS, numberPop, cumulative}) } // HYPGEOMdotDIST function returns the value of the hypergeometric distribution // for a specified number of successes from a population sample. The function // can calculate the cumulative distribution or the probability density // function. The syntax of the function is: // // HYPGEOM.DIST(sample_s,number_sample,population_s,number_pop,cumulative) func (fn *formulaFuncs) HYPGEOMdotDIST(argsList *list.List) formulaArg { args := fn.prepareHYPGEOMDISTArgs("HYPGEOM.DIST", argsList) if args.Type != ArgList { return args } sampleS, numberSample, populationS, numberPop, cumulative := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4] if cumulative.Number == 1 { var res float64 for i := 0; i <= int(sampleS.Number); i++ { res += binomCoeff(populationS.Number, float64(i)) * binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-float64(i)) / binomCoeff(numberPop.Number, numberSample.Number) } return newNumberFormulaArg(res) } return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) * binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) / binomCoeff(numberPop.Number, numberSample.Number)) } // HYPGEOMDIST function returns the value of the hypergeometric distribution // for a given number of successes from a sample of a population. The syntax // of the function is: // // HYPGEOMDIST(sample_s,number_sample,population_s,number_pop) func (fn *formulaFuncs) HYPGEOMDIST(argsList *list.List) formulaArg { args := fn.prepareHYPGEOMDISTArgs("HYPGEOMDIST", argsList) if args.Type != ArgList { return args } sampleS, numberSample, populationS, numberPop := args.List[0], args.List[1], args.List[2], args.List[3] return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) * binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) / binomCoeff(numberPop.Number, numberSample.Number)) } // INTERCEPT function calculates the intercept (the value at the intersection // of the y axis) of the linear regression line through a supplied set of x- // and y- values. The syntax of the function is: // // INTERCEPT(known_y's,known_x's) func (fn *formulaFuncs) INTERCEPT(argsList *list.List) formulaArg { return fn.pearsonProduct("INTERCEPT", 2, argsList) } // KURT function calculates the kurtosis of a supplied set of values. The // syntax of the function is: // // KURT(number1,[number2],...) func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "KURT requires at least 1 argument") } mean, stdev := fn.AVERAGE(argsList), fn.STDEV(argsList) if stdev.Number > 0 { count, summer := 0.0, 0.0 for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgString, ArgNumber: num := token.ToNumber() if num.Type == ArgError { continue } summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4) count++ case ArgList, ArgMatrix: for _, row := range token.ToList() { if row.Type == ArgNumber || row.Type == ArgString { num := row.ToNumber() if num.Type == ArgError { continue } summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4) count++ } } } } if count > 3 { return newNumberFormulaArg(summer*(count*(count+1)/((count-1)*(count-2)*(count-3))) - (3 * math.Pow(count-1, 2) / ((count - 2) * (count - 3)))) } } return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } // EXPONdotDIST function returns the value of the exponential distribution for // a give value of x. The user can specify whether the probability density // function or the cumulative distribution function is used. The syntax of the // Expondist function is: // // EXPON.DIST(x,lambda,cumulative) func (fn *formulaFuncs) EXPONdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "EXPON.DIST requires 3 arguments") } return fn.EXPONDIST(argsList) } // EXPONDIST function returns the value of the exponential distribution for a // give value of x. The user can specify whether the probability density // function or the cumulative distribution function is used. The syntax of the // Expondist function is: // // EXPONDIST(x,lambda,cumulative) func (fn *formulaFuncs) EXPONDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "EXPONDIST requires 3 arguments") } var x, lambda, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if lambda = argsList.Front().Next().Value.(formulaArg).ToNumber(); lambda.Type != ArgNumber { return lambda } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if x.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if lambda.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 1 { return newNumberFormulaArg(1 - math.Exp(-lambda.Number*x.Number)) } return newNumberFormulaArg(lambda.Number * math.Exp(-lambda.Number*x.Number)) } // FdotDIST function calculates the Probability Density Function or the // Cumulative Distribution Function for the F Distribution. This function is // frequently used to measure the degree of diversity between two data // sets. The syntax of the function is: // // F.DIST(x,deg_freedom1,deg_freedom2,cumulative) func (fn *formulaFuncs) FdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "F.DIST requires 4 arguments") } var x, deg1, deg2, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber { return deg1 } if deg2 = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber { return deg2 } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if x.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } maxDeg := math.Pow10(10) if deg1.Number < 1 || deg1.Number >= maxDeg { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if deg2.Number < 1 || deg2.Number >= maxDeg { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 1 { return newNumberFormulaArg(1 - getBetaDist(deg2.Number/(deg2.Number+deg1.Number*x.Number), deg2.Number/2, deg1.Number/2)) } return newNumberFormulaArg(math.Gamma((deg2.Number+deg1.Number)/2) / (math.Gamma(deg1.Number/2) * math.Gamma(deg2.Number/2)) * math.Pow(deg1.Number/deg2.Number, deg1.Number/2) * (math.Pow(x.Number, (deg1.Number-2)/2) / math.Pow(1+(deg1.Number/deg2.Number)*x.Number, (deg1.Number+deg2.Number)/2))) } // FDIST function calculates the (right-tailed) F Probability Distribution, // which measures the degree of diversity between two data sets. The syntax // of the function is: // // FDIST(x,deg_freedom1,deg_freedom2) func (fn *formulaFuncs) FDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "FDIST requires 3 arguments") } var x, deg1, deg2 formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber { return deg1 } if deg2 = argsList.Back().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber { return deg2 } if x.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } maxDeg := math.Pow10(10) if deg1.Number < 1 || deg1.Number >= maxDeg { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if deg2.Number < 1 || deg2.Number >= maxDeg { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New() args.PushBack(newNumberFormulaArg(deg1.Number * x.Number / (deg1.Number*x.Number + deg2.Number))) args.PushBack(newNumberFormulaArg(0.5 * deg1.Number)) args.PushBack(newNumberFormulaArg(0.5 * deg2.Number)) args.PushBack(newNumberFormulaArg(0)) args.PushBack(newNumberFormulaArg(1)) return newNumberFormulaArg(1 - fn.BETADIST(args).Number) } // FdotDISTdotRT function calculates the (right-tailed) F Probability // Distribution, which measures the degree of diversity between two data sets. // The syntax of the function is: // // F.DIST.RT(x,deg_freedom1,deg_freedom2) func (fn *formulaFuncs) FdotDISTdotRT(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "F.DIST.RT requires 3 arguments") } return fn.FDIST(argsList) } // prepareFinvArgs checking and prepare arguments for the formula functions // F.INV, F.INV.RT and FINV. func (fn *formulaFuncs) prepareFinvArgs(name string, argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name)) } var probability, d1, d2 formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if d1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); d1.Type != ArgNumber { return d1 } if d2 = argsList.Back().Value.(formulaArg).ToNumber(); d2.Type != ArgNumber { return d2 } if probability.Number <= 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if d1.Number < 1 || d1.Number >= math.Pow10(10) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if d2.Number < 1 || d2.Number >= math.Pow10(10) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newListFormulaArg([]formulaArg{probability, d1, d2}) } // FdotINV function calculates the inverse of the Cumulative F Distribution // for a supplied probability. The syntax of the F.Inv function is: // // F.INV(probability,deg_freedom1,deg_freedom2) func (fn *formulaFuncs) FdotINV(argsList *list.List) formulaArg { args := fn.prepareFinvArgs("F.INV", argsList) if args.Type != ArgList { return args } probability, d1, d2 := args.List[0], args.List[1], args.List[2] return newNumberFormulaArg((1/calcBetainv(1-probability.Number, d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) } // FdotINVdotRT function calculates the inverse of the (right-tailed) F // Probability Distribution for a supplied probability. The syntax of the // function is: // // F.INV.RT(probability,deg_freedom1,deg_freedom2) func (fn *formulaFuncs) FdotINVdotRT(argsList *list.List) formulaArg { args := fn.prepareFinvArgs("F.INV.RT", argsList) if args.Type != ArgList { return args } probability, d1, d2 := args.List[0], args.List[1], args.List[2] return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) } // FINV function calculates the inverse of the (right-tailed) F Probability // Distribution for a supplied probability. The syntax of the function is: // // FINV(probability,deg_freedom1,deg_freedom2) func (fn *formulaFuncs) FINV(argsList *list.List) formulaArg { args := fn.prepareFinvArgs("FINV", argsList) if args.Type != ArgList { return args } probability, d1, d2 := args.List[0], args.List[1], args.List[2] return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) } // FdotTEST function returns the F-Test for two supplied arrays. I.e. the // function returns the two-tailed probability that the variances in the two // supplied arrays are not significantly different. The syntax of the Ftest // function is: // // F.TEST(array1,array2) func (fn *formulaFuncs) FdotTEST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "F.TEST requires 2 arguments") } array1 := argsList.Front().Value.(formulaArg) array2 := argsList.Back().Value.(formulaArg) left, right := array1.ToList(), array2.ToList() collectMatrix := func(args []formulaArg) (n, accu float64) { var p, sum float64 for _, arg := range args { if num := arg.ToNumber(); num.Type == ArgNumber { x := num.Number - p y := x / (n + 1) p += y accu += n * x * y n++ sum += num.Number } } return } nums, accu := collectMatrix(left) f3 := nums - 1 if nums == 1 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } f1 := accu / (nums - 1) if f1 == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } nums, accu = collectMatrix(right) f4 := nums - 1 if nums == 1 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } f2 := accu / (nums - 1) if f2 == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } args := list.New() args.PushBack(newNumberFormulaArg(f1 / f2)) args.PushBack(newNumberFormulaArg(f3)) args.PushBack(newNumberFormulaArg(f4)) probability := (1 - fn.FDIST(args).Number) * 2 if probability > 1 { probability = 2 - probability } return newNumberFormulaArg(probability) } // FTEST function returns the F-Test for two supplied arrays. I.e. the function // returns the two-tailed probability that the variances in the two supplied // arrays are not significantly different. The syntax of the Ftest function // is: // // FTEST(array1,array2) func (fn *formulaFuncs) FTEST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "FTEST requires 2 arguments") } return fn.FdotTEST(argsList) } // LOGINV function calculates the inverse of the Cumulative Log-Normal // Distribution Function of x, for a supplied probability. The syntax of the // function is: // // LOGINV(probability,mean,standard_dev) func (fn *formulaFuncs) LOGINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "LOGINV requires 3 arguments") } var probability, mean, stdDev formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { return stdDev } if probability.Number <= 0 || probability.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if stdDev.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New() args.PushBack(probability) args.PushBack(newNumberFormulaArg(0)) args.PushBack(newNumberFormulaArg(1)) norminv := fn.NORMINV(args) return newNumberFormulaArg(math.Exp(mean.Number + stdDev.Number*norminv.Number)) } // LOGNORMdotINV function calculates the inverse of the Cumulative Log-Normal // Distribution Function of x, for a supplied probability. The syntax of the // function is: // // LOGNORM.INV(probability,mean,standard_dev) func (fn *formulaFuncs) LOGNORMdotINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.INV requires 3 arguments") } return fn.LOGINV(argsList) } // LOGNORMdotDIST function calculates the Log-Normal Probability Density // Function or the Cumulative Log-Normal Distribution Function for a supplied // value of x. The syntax of the function is: // // LOGNORM.DIST(x,mean,standard_dev,cumulative) func (fn *formulaFuncs) LOGNORMdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.DIST requires 4 arguments") } var x, mean, stdDev, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { return stdDev } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if x.Number <= 0 || stdDev.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 1 { args := list.New() args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number)) args.PushBack(newNumberFormulaArg(0)) args.PushBack(newNumberFormulaArg(1)) args.PushBack(cumulative) return fn.NORMDIST(args) } return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number * x.Number)) * math.Exp(0-(math.Pow(math.Log(x.Number)-mean.Number, 2)/(2*math.Pow(stdDev.Number, 2))))) } // LOGNORMDIST function calculates the Cumulative Log-Normal Distribution // Function at a supplied value of x. The syntax of the function is: // // LOGNORMDIST(x,mean,standard_dev) func (fn *formulaFuncs) LOGNORMDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "LOGNORMDIST requires 3 arguments") } var x, mean, stdDev formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { return stdDev } if x.Number <= 0 || stdDev.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New() args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number)) return fn.NORMSDIST(args) } // MODE function returns the statistical mode (the most frequently occurring // value) of a list of supplied numbers. If there are 2 or more most // frequently occurring values in the supplied data, the function returns the // lowest of these values The syntax of the function is: // // MODE(number1,[number2],...) func (fn *formulaFuncs) MODE(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "MODE requires at least 1 argument") } var values []float64 for arg := argsList.Front(); arg != nil; arg = arg.Next() { cells := arg.Value.(formulaArg) if cells.Type != ArgMatrix && cells.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } for _, cell := range cells.ToList() { if cell.Type == ArgNumber { values = append(values, cell.Number) } } } sort.Float64s(values) cnt := len(values) var count, modeCnt int var mode float64 for i := 0; i < cnt; i++ { count = 0 for j := 0; j < cnt; j++ { if j != i && values[j] == values[i] { count++ } } if count > modeCnt { modeCnt = count mode = values[i] } } if modeCnt == 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(mode) } // MODEdotMULT function returns a vertical array of the statistical modes // (the most frequently occurring values) within a list of supplied numbers. // The syntax of the function is: // // MODE.MULT(number1,[number2],...) func (fn *formulaFuncs) MODEdotMULT(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "MODE.MULT requires at least 1 argument") } var values []float64 for arg := argsList.Front(); arg != nil; arg = arg.Next() { cells := arg.Value.(formulaArg) if cells.Type != ArgMatrix && cells.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } for _, cell := range cells.ToList() { if cell.Type == ArgNumber { values = append(values, cell.Number) } } } sort.Float64s(values) cnt := len(values) var count, modeCnt int var mtx [][]formulaArg for i := 0; i < cnt; i++ { count = 0 for j := i + 1; j < cnt; j++ { if values[i] == values[j] { count++ } } if count > modeCnt { modeCnt = count mtx = [][]formulaArg{} mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])}) } else if count == modeCnt { mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])}) } } if modeCnt == 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newMatrixFormulaArg(mtx) } // MODEdotSNGL function returns the statistical mode (the most frequently // occurring value) within a list of supplied numbers. If there are 2 or more // most frequently occurring values in the supplied data, the function returns // the lowest of these values. The syntax of the function is: // // MODE.SNGL(number1,[number2],...) func (fn *formulaFuncs) MODEdotSNGL(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "MODE.SNGL requires at least 1 argument") } return fn.MODE(argsList) } // NEGBINOMdotDIST function calculates the probability mass function or the // cumulative distribution function for the Negative Binomial Distribution. // This gives the probability that there will be a given number of failures // before a required number of successes is achieved. The syntax of the // function is: // // NEGBINOM.DIST(number_f,number_s,probability_s,cumulative) func (fn *formulaFuncs) NEGBINOMdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOM.DIST requires 4 arguments") } var f, s, probability, cumulative formulaArg if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber { return f } if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { return s } if probability = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { return cumulative } if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 1 { return newNumberFormulaArg(1 - getBetaDist(1-probability.Number, f.Number+1, s.Number)) } return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number)) } // NEGBINOMDIST function calculates the Negative Binomial Distribution for a // given set of parameters. This gives the probability that there will be a // specified number of failures before a required number of successes is // achieved. The syntax of the function is: // // NEGBINOMDIST(number_f,number_s,probability_s) func (fn *formulaFuncs) NEGBINOMDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOMDIST requires 3 arguments") } var f, s, probability formulaArg if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber { return f } if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { return s } if probability = argsList.Back().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number)) } // NORMdotDIST function calculates the Normal Probability Density Function or // the Cumulative Normal Distribution. Function for a supplied set of // parameters. The syntax of the function is: // // NORM.DIST(x,mean,standard_dev,cumulative) func (fn *formulaFuncs) NORMdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "NORM.DIST requires 4 arguments") } return fn.NORMDIST(argsList) } // NORMDIST function calculates the Normal Probability Density Function or the // Cumulative Normal Distribution. Function for a supplied set of parameters. // The syntax of the function is: // // NORMDIST(x,mean,standard_dev,cumulative) func (fn *formulaFuncs) NORMDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "NORMDIST requires 4 arguments") } var x, mean, stdDev, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { return stdDev } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { return cumulative } if stdDev.Number < 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if cumulative.Number == 1 { return newNumberFormulaArg(0.5 * (1 + math.Erf((x.Number-mean.Number)/(stdDev.Number*math.Sqrt(2))))) } return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number)) * math.Exp(0-(math.Pow(x.Number-mean.Number, 2)/(2*(stdDev.Number*stdDev.Number))))) } // NORMdotINV function calculates the inverse of the Cumulative Normal // Distribution Function for a supplied value of x, and a supplied // distribution mean & standard deviation. The syntax of the function is: // // NORM.INV(probability,mean,standard_dev) func (fn *formulaFuncs) NORMdotINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "NORM.INV requires 3 arguments") } return fn.NORMINV(argsList) } // NORMINV function calculates the inverse of the Cumulative Normal // Distribution Function for a supplied value of x, and a supplied // distribution mean & standard deviation. The syntax of the function is: // // NORMINV(probability,mean,standard_dev) func (fn *formulaFuncs) NORMINV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "NORMINV requires 3 arguments") } var prob, mean, stdDev formulaArg if prob = argsList.Front().Value.(formulaArg).ToNumber(); prob.Type != ArgNumber { return prob } if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { return mean } if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { return stdDev } if prob.Number < 0 || prob.Number > 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if stdDev.Number < 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } inv, err := norminv(prob.Number) if err != nil { return newErrorFormulaArg(err.Error(), err.Error()) } return newNumberFormulaArg(inv*stdDev.Number + mean.Number) } // NORMdotSdotDIST function calculates the Standard Normal Cumulative // Distribution Function for a supplied value. The syntax of the function // is: // // NORM.S.DIST(z) func (fn *formulaFuncs) NORMdotSdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.DIST requires 2 numeric arguments") } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) args.PushBack(argsList.Back().Value.(formulaArg)) return fn.NORMDIST(args) } // NORMSDIST function calculates the Standard Normal Cumulative Distribution // Function for a supplied value. The syntax of the function is: // // NORMSDIST(z) func (fn *formulaFuncs) NORMSDIST(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "NORMSDIST requires 1 numeric argument") } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1, Boolean: true}) return fn.NORMDIST(args) } // NORMSINV function calculates the inverse of the Standard Normal Cumulative // Distribution Function for a supplied probability value. The syntax of the // function is: // // NORMSINV(probability) func (fn *formulaFuncs) NORMSINV(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "NORMSINV requires 1 numeric argument") } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) return fn.NORMINV(args) } // NORMdotSdotINV function calculates the inverse of the Standard Normal // Cumulative Distribution Function for a supplied probability value. The // syntax of the function is: // // NORM.S.INV(probability) func (fn *formulaFuncs) NORMdotSdotINV(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.INV requires 1 numeric argument") } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) return fn.NORMINV(args) } // norminv returns the inverse of the normal cumulative distribution for the // specified value. func norminv(p float64) (float64, error) { a := map[int]float64{ 1: -3.969683028665376e+01, 2: 2.209460984245205e+02, 3: -2.759285104469687e+02, 4: 1.383577518672690e+02, 5: -3.066479806614716e+01, 6: 2.506628277459239e+00, } b := map[int]float64{ 1: -5.447609879822406e+01, 2: 1.615858368580409e+02, 3: -1.556989798598866e+02, 4: 6.680131188771972e+01, 5: -1.328068155288572e+01, } c := map[int]float64{ 1: -7.784894002430293e-03, 2: -3.223964580411365e-01, 3: -2.400758277161838e+00, 4: -2.549732539343734e+00, 5: 4.374664141464968e+00, 6: 2.938163982698783e+00, } d := map[int]float64{ 1: 7.784695709041462e-03, 2: 3.224671290700398e-01, 3: 2.445134137142996e+00, 4: 3.754408661907416e+00, } pLow := 0.02425 // Use lower region approx. below this pHigh := 1 - pLow // Use upper region approx. above this if 0 < p && p < pLow { // Rational approximation for lower region. q := math.Sqrt(-2 * math.Log(p)) return (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) / ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil } else if pLow <= p && p <= pHigh { // Rational approximation for central region. q := p - 0.5 r := q * q f1 := ((((a[1]*r+a[2])*r+a[3])*r+a[4])*r + a[5]) * r f2 := (b[1]*r + b[2]) * r f3 := ((math.Nextafter(f2, f2)+b[3])*r + b[4]) * r f4 := (math.Nextafter(f3, f3) + b[5]) * r return (math.Nextafter(f1, f1) + a[6]) * q / (math.Nextafter(f4, f4) + 1), nil } else if pHigh < p && p < 1 { // Rational approximation for upper region. q := math.Sqrt(-2 * math.Log(1-p)) return -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) / ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil } return 0, errors.New(formulaErrorNUM) } // kth is an implementation of the formula functions LARGE and SMALL. func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) } array := argsList.Front().Value.(formulaArg).ToList() argK := argsList.Back().Value.(formulaArg).ToNumber() if argK.Type != ArgNumber { return argK } k := int(argK.Number) if k < 1 { return newErrorFormulaArg(formulaErrorNUM, "k should be > 0") } var data []float64 for _, arg := range array { if arg.Type == ArgNumber { data = append(data, arg.Number) } } if len(data) < k { return newErrorFormulaArg(formulaErrorNUM, "k should be <= length of array") } sort.Float64s(data) if name == "LARGE" { return newNumberFormulaArg(data[len(data)-k]) } return newNumberFormulaArg(data[k-1]) } // LARGE function returns the k'th largest value from an array of numeric // values. The syntax of the function is: // // LARGE(array,k) func (fn *formulaFuncs) LARGE(argsList *list.List) formulaArg { return fn.kth("LARGE", argsList) } // MAX function returns the largest value from a supplied set of numeric // values. The syntax of the function is: // // MAX(number1,[number2],...) func (fn *formulaFuncs) MAX(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "MAX requires at least 1 argument") } return fn.maxValue(false, argsList) } // MAXA function returns the largest value from a supplied set of numeric // values, while counting text and the logical value FALSE as the value 0 and // counting the logical value TRUE as the value 1. The syntax of the function // is: // // MAXA(number1,[number2],...) func (fn *formulaFuncs) MAXA(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "MAXA requires at least 1 argument") } return fn.maxValue(true, argsList) } // MAXIFS function returns the maximum value from a subset of values that are // specified according to one or more criteria. The syntax of the function // is: // // MAXIFS(max_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) func (fn *formulaFuncs) MAXIFS(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "MAXIFS requires at least 3 arguments") } if argsList.Len()%2 != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var args []formulaArg maxVal, maxRange := -math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } for _, ref := range formulaIfsMatch(args) { if num := maxRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && maxVal < num.Number { maxVal = num.Number } } if maxVal == -math.MaxFloat64 { maxVal = 0 } return newNumberFormulaArg(maxVal) } // calcListMatrixMax is part of the implementation max. func calcListMatrixMax(maxa bool, maxVal float64, arg formulaArg) float64 { for _, cell := range arg.ToList() { if cell.Type == ArgNumber && cell.Number > maxVal { if maxa && cell.Boolean || !cell.Boolean { maxVal = cell.Number } } } return maxVal } // maxValue is an implementation of the formula functions MAX and MAXA. func (fn *formulaFuncs) maxValue(maxa bool, argsList *list.List) formulaArg { maxVal := -math.MaxFloat64 for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: if !maxa && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { continue } else { num := arg.ToBool() if num.Type == ArgNumber && num.Number > maxVal { maxVal = num.Number continue } } num := arg.ToNumber() if num.Type != ArgError && num.Number > maxVal { maxVal = num.Number } case ArgNumber: if arg.Number > maxVal { maxVal = arg.Number } case ArgList, ArgMatrix: maxVal = calcListMatrixMax(maxa, maxVal, arg) case ArgError: return arg } } if maxVal == -math.MaxFloat64 { maxVal = 0 } return newNumberFormulaArg(maxVal) } // MEDIAN function returns the statistical median (the middle value) of a list // of supplied numbers. The syntax of the function is: // // MEDIAN(number1,[number2],...) func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument") } var values []float64 var median float64 for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: value := arg.ToNumber() if value.Type != ArgNumber { return value } values = append(values, value.Number) case ArgNumber: values = append(values, arg.Number) case ArgMatrix: for _, row := range arg.Matrix { for _, cell := range row { if cell.Type == ArgNumber { values = append(values, cell.Number) } } } } } if len(values) == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } sort.Float64s(values) if len(values)%2 == 0 { median = (values[len(values)/2-1] + values[len(values)/2]) / 2 } else { median = values[len(values)/2] } return newNumberFormulaArg(median) } // MIN function returns the smallest value from a supplied set of numeric // values. The syntax of the function is: // // MIN(number1,[number2],...) func (fn *formulaFuncs) MIN(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "MIN requires at least 1 argument") } return fn.minValue(false, argsList) } // MINA function returns the smallest value from a supplied set of numeric // values, while counting text and the logical value FALSE as the value 0 and // counting the logical value TRUE as the value 1. The syntax of the function // is: // // MINA(number1,[number2],...) func (fn *formulaFuncs) MINA(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "MINA requires at least 1 argument") } return fn.minValue(true, argsList) } // MINIFS function returns the minimum value from a subset of values that are // specified according to one or more criteria. The syntax of the function // is: // // MINIFS(min_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) func (fn *formulaFuncs) MINIFS(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "MINIFS requires at least 3 arguments") } if argsList.Len()%2 != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var args []formulaArg minVal, minRange := math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { args = append(args, arg.Value.(formulaArg)) } for _, ref := range formulaIfsMatch(args) { if num := minRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && minVal > num.Number { minVal = num.Number } } if minVal == math.MaxFloat64 { minVal = 0 } return newNumberFormulaArg(minVal) } // calcListMatrixMin is part of the implementation min. func calcListMatrixMin(mina bool, minVal float64, arg formulaArg) float64 { for _, cell := range arg.ToList() { if cell.Type == ArgNumber && cell.Number < minVal { if mina && cell.Boolean || !cell.Boolean { minVal = cell.Number } } } return minVal } // minValue is an implementation of the formula functions MIN and MINA. func (fn *formulaFuncs) minValue(mina bool, argsList *list.List) formulaArg { minVal := math.MaxFloat64 for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { case ArgString: if !mina && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { continue } else { num := arg.ToBool() if num.Type == ArgNumber && num.Number < minVal { minVal = num.Number continue } } num := arg.ToNumber() if num.Type != ArgError && num.Number < minVal { minVal = num.Number } case ArgNumber: if arg.Number < minVal { minVal = arg.Number } case ArgList, ArgMatrix: minVal = calcListMatrixMin(mina, minVal, arg) case ArgError: return arg } } if minVal == math.MaxFloat64 { minVal = 0 } return newNumberFormulaArg(minVal) } // pearsonProduct is an implementation of the formula functions FORECAST, // FORECAST.LINEAR, INTERCEPT, PEARSON, RSQ and SLOPE. func (fn *formulaFuncs) pearsonProduct(name string, n int, argsList *list.List) formulaArg { if argsList.Len() != n { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires %d arguments", name, n)) } var fx formulaArg array1 := argsList.Back().Value.(formulaArg).ToList() array2 := argsList.Front().Value.(formulaArg).ToList() if name == "PEARSON" || name == "RSQ" { array1, array2 = array2, array1 } if n == 3 { if fx = argsList.Front().Value.(formulaArg).ToNumber(); fx.Type != ArgNumber { return fx } array2 = argsList.Front().Next().Value.(formulaArg).ToList() } if len(array1) != len(array2) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var sum, deltaX, deltaY, x, y, length float64 for i := 0; i < len(array1); i++ { num1, num2 := array1[i], array2[i] if num1.Type != ArgNumber || num2.Type != ArgNumber { continue } x += num1.Number y += num2.Number length++ } x /= length y /= length for i := 0; i < len(array1); i++ { num1, num2 := array1[i], array2[i] if num1.Type != ArgNumber || num2.Type != ArgNumber { continue } sum += (num1.Number - x) * (num2.Number - y) deltaX += (num1.Number - x) * (num1.Number - x) deltaY += (num2.Number - y) * (num2.Number - y) } if sum*deltaX*deltaY == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(map[string]float64{ "FORECAST": y + sum/deltaX*(fx.Number-x), "FORECAST.LINEAR": y + sum/deltaX*(fx.Number-x), "INTERCEPT": y - sum/deltaX*x, "PEARSON": sum / math.Sqrt(deltaX*deltaY), "RSQ": math.Pow(sum/math.Sqrt(deltaX*deltaY), 2), "SLOPE": sum / deltaX, }[name]) } // PEARSON function calculates the Pearson Product-Moment Correlation // Coefficient for two sets of values. The syntax of the function is: // // PEARSON(array1,array2) func (fn *formulaFuncs) PEARSON(argsList *list.List) formulaArg { return fn.pearsonProduct("PEARSON", 2, argsList) } // PERCENTILEdotEXC function returns the k'th percentile (i.e. the value below // which k% of the data values fall) for a supplied range of values and a // supplied k (between 0 & 1 exclusive).The syntax of the function is: // // PERCENTILE.EXC(array,k) func (fn *formulaFuncs) PERCENTILEdotEXC(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.EXC requires 2 arguments") } array := argsList.Front().Value.(formulaArg).ToList() k := argsList.Back().Value.(formulaArg).ToNumber() if k.Type != ArgNumber { return k } if k.Number <= 0 || k.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } var numbers []float64 for _, arg := range array { if arg.Type == ArgError { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if arg.Type == ArgNumber { numbers = append(numbers, arg.Number) } } cnt := len(numbers) sort.Float64s(numbers) idx := k.Number * (float64(cnt) + 1) base := math.Floor(idx) next := base - 1 proportion := math.Nextafter(idx, idx) - base return newNumberFormulaArg(numbers[int(next)] + ((numbers[int(base)] - numbers[int(next)]) * proportion)) } // PERCENTILEdotINC function returns the k'th percentile (i.e. the value below // which k% of the data values fall) for a supplied range of values and a // supplied k. The syntax of the function is: // // PERCENTILE.INC(array,k) func (fn *formulaFuncs) PERCENTILEdotINC(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.INC requires 2 arguments") } return fn.PERCENTILE(argsList) } // PERCENTILE function returns the k'th percentile (i.e. the value below which // k% of the data values fall) for a supplied range of values and a supplied // k. The syntax of the function is: // // PERCENTILE(array,k) func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE requires 2 arguments") } array := argsList.Front().Value.(formulaArg).ToList() k := argsList.Back().Value.(formulaArg).ToNumber() if k.Type != ArgNumber { return k } if k.Number < 0 || k.Number > 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var numbers []float64 for _, arg := range array { if arg.Type == ArgError { return arg } if arg.Type == ArgNumber { numbers = append(numbers, arg.Number) } } cnt := len(numbers) sort.Float64s(numbers) idx := k.Number * (float64(cnt) - 1) base := math.Floor(idx) if idx == base { return newNumberFormulaArg(numbers[int(idx)]) } next := base + 1 proportion := math.Nextafter(idx, idx) - base return newNumberFormulaArg(numbers[int(base)] + ((numbers[int(next)] - numbers[int(base)]) * proportion)) } // percentrank is an implementation of the formula functions PERCENTRANK and // PERCENTRANK.INC. func (fn *formulaFuncs) percentrank(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 && argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 or 3 arguments", name)) } array := argsList.Front().Value.(formulaArg).ToList() x := argsList.Front().Next().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } var numbers []float64 for _, arg := range array { if arg.Type == ArgError { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if arg.Type == ArgNumber { numbers = append(numbers, arg.Number) } } cnt := len(numbers) sort.Float64s(numbers) if x.Number < numbers[0] || x.Number > numbers[cnt-1] { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } pos, significance := float64(inFloat64Slice(numbers, x.Number)), newNumberFormulaArg(3) if argsList.Len() == 3 { if significance = argsList.Back().Value.(formulaArg).ToNumber(); significance.Type != ArgNumber { return significance } if significance.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s arguments significance should be > 1", name)) } } if pos == -1 { pos = 0 cmp := numbers[0] for cmp < x.Number { pos++ cmp = numbers[int(pos)] } pos-- pos += (x.Number - numbers[int(pos)]) / (cmp - numbers[int(pos)]) } pow := math.Pow(10, significance.Number) digit := pow * pos / (float64(cnt) - 1) if name == "PERCENTRANK.EXC" { digit = pow * (pos + 1) / (float64(cnt) + 1) } return newNumberFormulaArg(math.Floor(digit) / pow) } // PERCENTRANKdotEXC function calculates the relative position, between 0 and // 1 (exclusive), of a specified value within a supplied array. The syntax of // the function is: // // PERCENTRANK.EXC(array,x,[significance]) func (fn *formulaFuncs) PERCENTRANKdotEXC(argsList *list.List) formulaArg { return fn.percentrank("PERCENTRANK.EXC", argsList) } // PERCENTRANKdotINC function calculates the relative position, between 0 and // 1 (inclusive), of a specified value within a supplied array.The syntax of // the function is: // // PERCENTRANK.INC(array,x,[significance]) func (fn *formulaFuncs) PERCENTRANKdotINC(argsList *list.List) formulaArg { return fn.percentrank("PERCENTRANK.INC", argsList) } // PERCENTRANK function calculates the relative position of a specified value, // within a set of values, as a percentage. The syntax of the function is: // // PERCENTRANK(array,x,[significance]) func (fn *formulaFuncs) PERCENTRANK(argsList *list.List) formulaArg { return fn.percentrank("PERCENTRANK", argsList) } // PERMUT function calculates the number of permutations of a specified number // of objects from a set of objects. The syntax of the function is: // // PERMUT(number,number_chosen) func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "PERMUT requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() chosen := argsList.Back().Value.(formulaArg).ToNumber() if number.Type != ArgNumber { return number } if chosen.Type != ArgNumber { return chosen } if number.Number < chosen.Number { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number))) } // PERMUTATIONA function calculates the number of permutations, with // repetitions, of a specified number of objects from a set. The syntax of // the function is: // // PERMUTATIONA(number,number_chosen) func (fn *formulaFuncs) PERMUTATIONA(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "PERMUTATIONA requires 2 numeric arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() chosen := argsList.Back().Value.(formulaArg).ToNumber() if number.Type != ArgNumber { return number } if chosen.Type != ArgNumber { return chosen } num, numChosen := math.Floor(number.Number), math.Floor(chosen.Number) if num < 0 || numChosen < 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(math.Pow(num, numChosen)) } // PHI function returns the value of the density function for a standard normal // distribution for a supplied number. The syntax of the function is: // // PHI(x) func (fn *formulaFuncs) PHI(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "PHI requires 1 argument") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } return newNumberFormulaArg(0.39894228040143268 * math.Exp(-(x.Number*x.Number)/2)) } // QUARTILE function returns a requested quartile of a supplied range of // values. The syntax of the function is: // // QUARTILE(array,quart) func (fn *formulaFuncs) QUARTILE(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE requires 2 arguments") } quart := argsList.Back().Value.(formulaArg).ToNumber() if quart.Type != ArgNumber { return quart } if quart.Number < 0 || quart.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(newNumberFormulaArg(quart.Number / 4)) return fn.PERCENTILE(args) } // QUARTILEdotEXC function returns a requested quartile of a supplied range of // values, based on a percentile range of 0 to 1 exclusive. The syntax of the // function is: // // QUARTILE.EXC(array,quart) func (fn *formulaFuncs) QUARTILEdotEXC(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.EXC requires 2 arguments") } quart := argsList.Back().Value.(formulaArg).ToNumber() if quart.Type != ArgNumber { return quart } if quart.Number <= 0 || quart.Number >= 4 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args := list.New().Init() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(newNumberFormulaArg(quart.Number / 4)) return fn.PERCENTILEdotEXC(args) } // QUARTILEdotINC function returns a requested quartile of a supplied range of // values. The syntax of the function is: // // QUARTILE.INC(array,quart) func (fn *formulaFuncs) QUARTILEdotINC(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.INC requires 2 arguments") } return fn.QUARTILE(argsList) } // rank is an implementation of the formula functions RANK and RANK.EQ. func (fn *formulaFuncs) rank(name string, argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 3 arguments", name)) } num := argsList.Front().Value.(formulaArg).ToNumber() if num.Type != ArgNumber { return num } var arr []float64 for _, arg := range argsList.Front().Next().Value.(formulaArg).ToList() { if arg.Type == ArgNumber { arr = append(arr, arg.Number) } } sort.Float64s(arr) order := newNumberFormulaArg(0) if argsList.Len() == 3 { if order = argsList.Back().Value.(formulaArg).ToNumber(); order.Type != ArgNumber { return order } } if order.Number == 0 { sort.Sort(sort.Reverse(sort.Float64Slice(arr))) } if idx := inFloat64Slice(arr, num.Number); idx != -1 { return newNumberFormulaArg(float64(idx + 1)) } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // RANKdotEQ function returns the statistical rank of a given value, within a // supplied array of values. If there are duplicate values in the list, these // are given the same rank. The syntax of the function is: // // RANK.EQ(number,ref,[order]) func (fn *formulaFuncs) RANKdotEQ(argsList *list.List) formulaArg { return fn.rank("RANK.EQ", argsList) } // RANK function returns the statistical rank of a given value, within a // supplied array of values. If there are duplicate values in the list, these // are given the same rank. The syntax of the function is: // // RANK(number,ref,[order]) func (fn *formulaFuncs) RANK(argsList *list.List) formulaArg { return fn.rank("RANK", argsList) } // RSQ function calculates the square of the Pearson Product-Moment Correlation // Coefficient for two supplied sets of values. The syntax of the function // is: // // RSQ(known_y's,known_x's) func (fn *formulaFuncs) RSQ(argsList *list.List) formulaArg { return fn.pearsonProduct("RSQ", 2, argsList) } // skew is an implementation of the formula functions SKEW and SKEW.P. func (fn *formulaFuncs) skew(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } mean := fn.AVERAGE(argsList) var stdDev formulaArg var count, summer float64 if name == "SKEW" { stdDev = fn.STDEV(argsList) } else { stdDev = fn.STDEVP(argsList) } for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgNumber, ArgString: num := token.ToNumber() if num.Type == ArgError { return num } summer += math.Pow((num.Number-mean.Number)/stdDev.Number, 3) count++ case ArgList, ArgMatrix: for _, cell := range token.ToList() { if cell.Type != ArgNumber { continue } summer += math.Pow((cell.Number-mean.Number)/stdDev.Number, 3) count++ } } } if count > 2 { if name == "SKEW" { return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2)))) } return newNumberFormulaArg(summer / count) } return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } // SKEW function calculates the skewness of the distribution of a supplied set // of values. The syntax of the function is: // // SKEW(number1,[number2],...) func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg { return fn.skew("SKEW", argsList) } // SKEWdotP function calculates the skewness of the distribution of a supplied // set of values. The syntax of the function is: // // SKEW.P(number1,[number2],...) func (fn *formulaFuncs) SKEWdotP(argsList *list.List) formulaArg { return fn.skew("SKEW.P", argsList) } // SLOPE returns the slope of the linear regression line through data points in // known_y's and known_x's. The slope is the vertical distance divided by the // horizontal distance between any two points on the line, which is the rate // of change along the regression line. The syntax of the function is: // // SLOPE(known_y's,known_x's) func (fn *formulaFuncs) SLOPE(argsList *list.List) formulaArg { return fn.pearsonProduct("SLOPE", 2, argsList) } // SMALL function returns the k'th smallest value from an array of numeric // values. The syntax of the function is: // // SMALL(array,k) func (fn *formulaFuncs) SMALL(argsList *list.List) formulaArg { return fn.kth("SMALL", argsList) } // STANDARDIZE function returns a normalized value of a distribution that is // characterized by a supplied mean and standard deviation. The syntax of the // function is: // // STANDARDIZE(x,mean,standard_dev) func (fn *formulaFuncs) STANDARDIZE(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "STANDARDIZE requires 3 arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() if x.Type != ArgNumber { return x } mean := argsList.Front().Next().Value.(formulaArg).ToNumber() if mean.Type != ArgNumber { return mean } stdDev := argsList.Back().Value.(formulaArg).ToNumber() if stdDev.Type != ArgNumber { return stdDev } if stdDev.Number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg((x.Number - mean.Number) / stdDev.Number) } // stdevp is an implementation of the formula functions STDEVP, STDEV.P and // STDEVPA. func (fn *formulaFuncs) stdevp(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } fnName := "VARP" if name == "STDEVPA" { fnName = "VARPA" } varp := fn.vars(fnName, argsList) if varp.Type != ArgNumber { return varp } return newNumberFormulaArg(math.Sqrt(varp.Number)) } // STDEVP function calculates the standard deviation of a supplied set of // values. The syntax of the function is: // // STDEVP(number1,[number2],...) func (fn *formulaFuncs) STDEVP(argsList *list.List) formulaArg { return fn.stdevp("STDEVP", argsList) } // STDEVdotP function calculates the standard deviation of a supplied set of // values. // // STDEV.P( number1, [number2], ... ) func (fn *formulaFuncs) STDEVdotP(argsList *list.List) formulaArg { return fn.stdevp("STDEV.P", argsList) } // STDEVPA function calculates the standard deviation of a supplied set of // values. The syntax of the function is: // // STDEVPA(number1,[number2],...) func (fn *formulaFuncs) STDEVPA(argsList *list.List) formulaArg { return fn.stdevp("STDEVPA", argsList) } // STEYX function calculates the standard error for the line of best fit, // through a supplied set of x- and y- values. The syntax of the function is: // // STEYX(known_y's,known_x's) func (fn *formulaFuncs) STEYX(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "STEYX requires 2 arguments") } array1 := argsList.Back().Value.(formulaArg).ToList() array2 := argsList.Front().Value.(formulaArg).ToList() if len(array1) != len(array2) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var count, sumX, sumY, squareX, squareY, sigmaXY float64 for i := 0; i < len(array1); i++ { num1, num2 := array1[i], array2[i] if num1.Type != ArgNumber || num2.Type != ArgNumber { continue } sumX += num1.Number sumY += num2.Number squareX += num1.Number * num1.Number squareY += num2.Number * num2.Number sigmaXY += num1.Number * num2.Number count++ } if count < 3 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } dx, dy := sumX/count, sumY/count sigma1 := squareY - 2*dy*sumY + count*dy*dy sigma2 := sigmaXY - dy*sumX - sumY*dx + count*dy*dx sigma3 := squareX - 2*dx*sumX + count*dx*dx return newNumberFormulaArg(math.Sqrt((sigma1 - (sigma2*sigma2)/sigma3) / (count - 2))) } // getTDist is an implementation for the beta distribution probability density // function. func getTDist(T, fDF, nType float64) float64 { var res float64 switch nType { case 1: res = 0.5 * getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) case 2: res = getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) case 3: res = math.Pow(1+(T*T/fDF), -(fDF+1)/2) / (math.Sqrt(fDF) * getBeta(0.5, fDF/2.0)) case 4: X := fDF / (T*T + fDF) R := 0.5 * getBetaDist(X, 0.5*fDF, 0.5) res = 1 - R if T < 0 { res = R } } return res } // TdotDIST function calculates the one-tailed Student's T Distribution, which // is a continuous probability distribution that is frequently used for // testing hypotheses on small sample data sets. The syntax of the function // is: // // T.DIST(x,degrees_freedom,cumulative) func (fn *formulaFuncs) TdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "T.DIST requires 3 arguments") } var x, degrees, cumulative formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { return cumulative } if cumulative.Number == 1 && degrees.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if cumulative.Number == 0 { if degrees.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if degrees.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 3)) } return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 4)) } // TdotDISTdot2T function calculates the two-tailed Student's T Distribution, // which is a continuous probability distribution that is frequently used for // testing hypotheses on small sample data sets. The syntax of the function // is: // // T.DIST.2T(x,degrees_freedom) func (fn *formulaFuncs) TdotDISTdot2T(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.2T requires 2 arguments") } var x, degrees formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if x.Number < 0 || degrees.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 2)) } // TdotDISTdotRT function calculates the right-tailed Student's T Distribution, // which is a continuous probability distribution that is frequently used for // testing hypotheses on small sample data sets. The syntax of the function // is: // // T.DIST.RT(x,degrees_freedom) func (fn *formulaFuncs) TdotDISTdotRT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.RT requires 2 arguments") } var x, degrees formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if degrees.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } v := getTDist(x.Number, degrees.Number, 1) if x.Number < 0 { v = 1 - v } return newNumberFormulaArg(v) } // TDIST function calculates the Student's T Distribution, which is a // continuous probability distribution that is frequently used for testing // hypotheses on small sample data sets. The syntax of the function is: // // TDIST(x,degrees_freedom,tails) func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "TDIST requires 3 arguments") } var x, degrees, tails formulaArg if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { return x } if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if tails = argsList.Back().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber { return tails } if x.Number < 0 || degrees.Number < 1 || (tails.Number != 1 && tails.Number != 2) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(getTDist(x.Number, degrees.Number, tails.Number)) } // TdotINV function calculates the left-tailed inverse of the Student's T // Distribution, which is a continuous probability distribution that is // frequently used for testing hypotheses on small sample data sets. The // syntax of the function is: // // T.INV(probability,degrees_freedom) func (fn *formulaFuncs) TdotINV(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "T.INV requires 2 arguments") } var probability, degrees formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if probability.Number <= 0 || probability.Number >= 1 || degrees.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if probability.Number < 0.5 { return newNumberFormulaArg(-calcIterateInverse(calcInverseIterator{ name: "T.INV", fp: 1 - probability.Number, fDF: degrees.Number, nT: 4, }, degrees.Number/2, degrees.Number)) } return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ name: "T.INV", fp: probability.Number, fDF: degrees.Number, nT: 4, }, degrees.Number/2, degrees.Number)) } // TdotINVdot2T function calculates the inverse of the two-tailed Student's T // Distribution, which is a continuous probability distribution that is // frequently used for testing hypotheses on small sample data sets. The // syntax of the function is: // // T.INV.2T(probability,degrees_freedom) func (fn *formulaFuncs) TdotINVdot2T(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "T.INV.2T requires 2 arguments") } var probability, degrees formulaArg if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { return probability } if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { return degrees } if probability.Number <= 0 || probability.Number > 1 || degrees.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ name: "T.INV.2T", fp: probability.Number, fDF: degrees.Number, nT: 2, }, degrees.Number/2, degrees.Number)) } // TINV function calculates the inverse of the two-tailed Student's T // Distribution, which is a continuous probability distribution that is // frequently used for testing hypotheses on small sample data sets. The // syntax of the function is: // // TINV(probability,degrees_freedom) func (fn *formulaFuncs) TINV(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "TINV requires 2 arguments") } return fn.TdotINVdot2T(argsList) } // TREND function calculates the linear trend line through a given set of // y-values and (optionally), a given set of x-values. The function then // extends the linear trendline to calculate additional y-values for a further // supplied set of new x-values. The syntax of the function is: // // TREND(known_y's,[known_x's],[new_x's],[const]) func (fn *formulaFuncs) TREND(argsList *list.List) formulaArg { return fn.trendGrowth("TREND", argsList) } // tTest calculates the probability associated with the Student's T Test. func tTest(bTemplin bool, mtx1, mtx2 [][]formulaArg, c1, c2, r1, r2 int) (float64, float64, bool) { var cnt1, cnt2, sum1, sumSqr1, sum2, sumSqr2 float64 var fVal formulaArg for i := 0; i < c1; i++ { for j := 0; j < r1; j++ { if fVal = mtx1[i][j]; fVal.Type == ArgNumber { sum1 += fVal.Number sumSqr1 += fVal.Number * fVal.Number cnt1++ } } } for i := 0; i < c2; i++ { for j := 0; j < r2; j++ { if fVal = mtx2[i][j]; fVal.Type == ArgNumber { sum2 += fVal.Number sumSqr2 += fVal.Number * fVal.Number cnt2++ } } } if cnt1 < 2.0 || cnt2 < 2.0 { return 0, 0, false } if bTemplin { fS1 := (sumSqr1 - sum1*sum1/cnt1) / (cnt1 - 1) / cnt1 fS2 := (sumSqr2 - sum2*sum2/cnt2) / (cnt2 - 1) / cnt2 if fS1+fS2 == 0 { return 0, 0, false } c := fS1 / (fS1 + fS2) return math.Abs(sum1/cnt1-sum2/cnt2) / math.Sqrt(fS1+fS2), 1 / (c*c/(cnt1-1) + (1-c)*(1-c)/(cnt2-1)), true } fS1 := (sumSqr1 - sum1*sum1/cnt1) / (cnt1 - 1) fS2 := (sumSqr2 - sum2*sum2/cnt2) / (cnt2 - 1) return math.Abs(sum1/cnt1-sum2/cnt2) / math.Sqrt((cnt1-1)*fS1+(cnt2-1)*fS2) * math.Sqrt(cnt1*cnt2*(cnt1+cnt2-2)/(cnt1+cnt2)), cnt1 + cnt2 - 2, true } // tTest is an implementation of the formula function TTEST. func (fn *formulaFuncs) tTest(mtx1, mtx2 [][]formulaArg, fTails, fTyp float64) formulaArg { var fT, fF float64 c1, c2, r1, r2, ok := len(mtx1), len(mtx2), len(mtx1[0]), len(mtx2[0]), true if fTyp == 1 { if c1 != c2 || r1 != r2 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } var cnt, sum1, sum2, sumSqrD float64 var fVal1, fVal2 formulaArg for i := 0; i < c1; i++ { for j := 0; j < r1; j++ { fVal1, fVal2 = mtx1[i][j], mtx2[i][j] if fVal1.Type != ArgNumber || fVal2.Type != ArgNumber { continue } sum1 += fVal1.Number sum2 += fVal2.Number sumSqrD += (fVal1.Number - fVal2.Number) * (fVal1.Number - fVal2.Number) cnt++ } } if cnt < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } sumD := sum1 - sum2 divider := cnt*sumSqrD - sumD*sumD if divider == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } fT = math.Abs(sumD) * math.Sqrt((cnt-1)/divider) fF = cnt - 1 } else if fTyp == 2 { fT, fF, ok = tTest(false, mtx1, mtx2, c1, c2, r1, r2) } else { fT, fF, ok = tTest(true, mtx1, mtx2, c1, c2, r1, r2) } if !ok { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(getTDist(fT, fF, fTails)) } // TTEST function calculates the probability associated with the Student's T // Test, which is commonly used for identifying whether two data sets are // likely to have come from the same two underlying populations with the same // mean. The syntax of the function is: // // TTEST(array1,array2,tails,type) func (fn *formulaFuncs) TTEST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "TTEST requires 4 arguments") } var array1, array2, tails, typeArg formulaArg array1 = argsList.Front().Value.(formulaArg) array2 = argsList.Front().Next().Value.(formulaArg) if tails = argsList.Front().Next().Next().Value.(formulaArg); tails.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if typeArg = argsList.Back().Value.(formulaArg); typeArg.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if len(array1.Matrix) == 0 || len(array2.Matrix) == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if tails.Number != 1 && tails.Number != 2 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if typeArg.Number != 1 && typeArg.Number != 2 && typeArg.Number != 3 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return fn.tTest(array1.Matrix, array2.Matrix, tails.Number, typeArg.Number) } // TdotTEST function calculates the probability associated with the Student's T // Test, which is commonly used for identifying whether two data sets are // likely to have come from the same two underlying populations with the same // mean. The syntax of the function is: // // T.TEST(array1,array2,tails,type) func (fn *formulaFuncs) TdotTEST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "T.TEST requires 4 arguments") } return fn.TTEST(argsList) } // TRIMMEAN function calculates the trimmed mean (or truncated mean) of a // supplied set of values. The syntax of the function is: // // TRIMMEAN(array,percent) func (fn *formulaFuncs) TRIMMEAN(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "TRIMMEAN requires 2 arguments") } percent := argsList.Back().Value.(formulaArg).ToNumber() if percent.Type != ArgNumber { return percent } if percent.Number < 0 || percent.Number >= 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } var arr []float64 arrArg := argsList.Front().Value.(formulaArg).ToList() for _, cell := range arrArg { if cell.Type != ArgNumber { continue } arr = append(arr, cell.Number) } discard := math.Floor(float64(len(arr)) * percent.Number / 2) sort.Float64s(arr) for i := 0; i < int(discard); i++ { if len(arr) > 0 { arr = arr[1:] } if len(arr) > 0 { arr = arr[:len(arr)-1] } } args := list.New().Init() for _, ele := range arr { args.PushBack(newNumberFormulaArg(ele)) } return fn.AVERAGE(args) } // vars is an implementation of the formula functions VAR, VARA, VARP, VAR.P // VAR.S and VARPA. func (fn *formulaFuncs) vars(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } summerA, summerB, count := 0.0, 0.0, 0.0 minimum := 0.0 if name == "VAR" || name == "VAR.S" || name == "VARA" { minimum = 1.0 } for arg := argsList.Front(); arg != nil; arg = arg.Next() { for _, token := range arg.Value.(formulaArg).ToList() { if token.Value() == "" { continue } num := token.ToNumber() if token.Value() != "TRUE" && num.Type == ArgNumber { summerA += num.Number * num.Number summerB += num.Number count++ continue } num = token.ToBool() if num.Type == ArgNumber { summerA += num.Number * num.Number summerB += num.Number count++ continue } if name == "VARA" || name == "VARPA" { count++ } } } if count > minimum { summerA *= count summerB *= summerB return newNumberFormulaArg((summerA - summerB) / (count * (count - minimum))) } return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } // VAR function returns the sample variance of a supplied set of values. The // syntax of the function is: // // VAR(number1,[number2],...) func (fn *formulaFuncs) VAR(argsList *list.List) formulaArg { return fn.vars("VAR", argsList) } // VARA function calculates the sample variance of a supplied set of values. // The syntax of the function is: // // VARA(number1,[number2],...) func (fn *formulaFuncs) VARA(argsList *list.List) formulaArg { return fn.vars("VARA", argsList) } // VARP function returns the Variance of a given set of values. The syntax of // the function is: // // VARP(number1,[number2],...) func (fn *formulaFuncs) VARP(argsList *list.List) formulaArg { return fn.vars("VARP", argsList) } // VARdotP function returns the Variance of a given set of values. The syntax // of the function is: // // VAR.P(number1,[number2],...) func (fn *formulaFuncs) VARdotP(argsList *list.List) formulaArg { return fn.vars("VAR.P", argsList) } // VARdotS function calculates the sample variance of a supplied set of // values. The syntax of the function is: // // VAR.S(number1,[number2],...) func (fn *formulaFuncs) VARdotS(argsList *list.List) formulaArg { return fn.vars("VAR.S", argsList) } // VARPA function returns the Variance of a given set of values. The syntax of // the function is: // // VARPA(number1,[number2],...) func (fn *formulaFuncs) VARPA(argsList *list.List) formulaArg { return fn.vars("VARPA", argsList) } // WEIBULL function calculates the Weibull Probability Density Function or the // Weibull Cumulative Distribution Function for a supplied set of parameters. // The syntax of the function is: // // WEIBULL(x,alpha,beta,cumulative) func (fn *formulaFuncs) WEIBULL(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL requires 4 arguments") } x := argsList.Front().Value.(formulaArg).ToNumber() alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() beta := argsList.Back().Prev().Value.(formulaArg).ToNumber() if alpha.Type == ArgNumber && beta.Type == ArgNumber && x.Type == ArgNumber { if alpha.Number < 0 || alpha.Number <= 0 || beta.Number <= 0 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } cumulative := argsList.Back().Value.(formulaArg).ToBool() if cumulative.Boolean && cumulative.Number == 1 { return newNumberFormulaArg(1 - math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number))) } return newNumberFormulaArg((alpha.Number / math.Pow(beta.Number, alpha.Number)) * math.Pow(x.Number, alpha.Number-1) * math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number))) } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } // WEIBULLdotDIST function calculates the Weibull Probability Density Function // or the Weibull Cumulative Distribution Function for a supplied set of // parameters. The syntax of the function is: // // WEIBULL.DIST(x,alpha,beta,cumulative) func (fn *formulaFuncs) WEIBULLdotDIST(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL.DIST requires 4 arguments") } return fn.WEIBULL(argsList) } // ZdotTEST function calculates the one-tailed probability value of the // Z-Test. The syntax of the function is: // // Z.TEST(array,x,[sigma]) func (fn *formulaFuncs) ZdotTEST(argsList *list.List) formulaArg { argsLen := argsList.Len() if argsLen < 2 { return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST requires at least 2 arguments") } if argsLen > 3 { return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST accepts at most 3 arguments") } return fn.ZTEST(argsList) } // ZTEST function calculates the one-tailed probability value of the Z-Test. // The syntax of the function is: // // ZTEST(array,x,[sigma]) func (fn *formulaFuncs) ZTEST(argsList *list.List) formulaArg { argsLen := argsList.Len() if argsLen < 2 { return newErrorFormulaArg(formulaErrorVALUE, "ZTEST requires at least 2 arguments") } if argsLen > 3 { return newErrorFormulaArg(formulaErrorVALUE, "ZTEST accepts at most 3 arguments") } arrArg, arrArgs := argsList.Front().Value.(formulaArg), list.New() arrArgs.PushBack(arrArg) arr := fn.AVERAGE(arrArgs) if arr.Type == ArgError { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } x := argsList.Front().Next().Value.(formulaArg).ToNumber() if x.Type == ArgError { return x } sigma := argsList.Back().Value.(formulaArg).ToNumber() if sigma.Type == ArgError { return sigma } if argsLen != 3 { sigma = fn.STDEV(arrArgs).ToNumber() } normsdistArg := list.New() div := sigma.Number / math.Sqrt(float64(len(arrArg.ToList()))) if div == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } normsdistArg.PushBack(newNumberFormulaArg((arr.Number - x.Number) / div)) return newNumberFormulaArg(1 - fn.NORMSDIST(normsdistArg).Number) } // Information Functions // ERRORdotTYPE function receives an error value and returns an integer, that // tells you the type of the supplied error. The syntax of the function is: // // ERROR.TYPE(error_val) func (fn *formulaFuncs) ERRORdotTYPE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ERROR.TYPE requires 1 argument") } token := argsList.Front().Value.(formulaArg) if token.Type == ArgError { for i, errType := range []string{ formulaErrorNULL, formulaErrorDIV, formulaErrorVALUE, formulaErrorREF, formulaErrorNAME, formulaErrorNUM, formulaErrorNA, } { if errType == token.String { return newNumberFormulaArg(float64(i) + 1) } } } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // ISBLANK function tests if a specified cell is blank (empty) and if so, // returns TRUE; Otherwise the function returns FALSE. The syntax of the // function is: // // ISBLANK(value) func (fn *formulaFuncs) ISBLANK(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISBLANK requires 1 argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgUnknown, ArgEmpty: return newBoolFormulaArg(true) default: return newBoolFormulaArg(false) } } // ISERR function tests if an initial supplied expression (or value) returns // any Excel Error, except the #N/A error. If so, the function returns the // logical value TRUE; If the supplied value is not an error or is the #N/A // error, the ISERR function returns FALSE. The syntax of the function is: // // ISERR(value) func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISERR requires 1 argument") } token := argsList.Front().Value.(formulaArg) result := false if token.Type == ArgError { for _, errType := range []string{ formulaErrorDIV, formulaErrorNAME, formulaErrorNUM, formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA, } { if errType == token.String { result = true } } } return newBoolFormulaArg(result) } // ISERROR function tests if an initial supplied expression (or value) returns // an Excel Error, and if so, returns the logical value TRUE; Otherwise the // function returns FALSE. The syntax of the function is: // // ISERROR(value) func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISERROR requires 1 argument") } token := argsList.Front().Value.(formulaArg) result := false if token.Type == ArgError { for _, errType := range []string{ formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM, formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA, } { if errType == token.String { result = true } } } return newBoolFormulaArg(result) } // ISEVEN function tests if a supplied number (or numeric expression) // evaluates to an even number, and if so, returns TRUE; Otherwise, the // function returns FALSE. The syntax of the function is: // // ISEVEN(value) func (fn *formulaFuncs) ISEVEN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISEVEN requires 1 argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgEmpty: return newBoolFormulaArg(true) case ArgNumber, ArgString: num := token.ToNumber() if num.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if num.Number == 1 { return newBoolFormulaArg(false) } return newBoolFormulaArg(num.Number == num.Number/2*2) default: return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } // ISFORMULA function tests if a specified cell contains a formula, and if so, // returns TRUE; Otherwise, the function returns FALSE. The syntax of the // function is: // // ISFORMULA(reference) func (fn *formulaFuncs) ISFORMULA(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISFORMULA requires 1 argument") } arg := argsList.Front().Value.(formulaArg) if arg.cellRefs != nil && arg.cellRefs.Len() == 1 { ref := arg.cellRefs.Front().Value.(cellRef) cell, _ := CoordinatesToCellName(ref.Col, ref.Row) if formula, _ := fn.f.GetCellFormula(ref.Sheet, cell); len(formula) > 0 { return newBoolFormulaArg(true) } } return newBoolFormulaArg(false) } // ISLOGICAL function tests if a supplied value (or expression) returns a // logical value (i.e. evaluates to True or False). If so, the function // returns TRUE; Otherwise, it returns FALSE. The syntax of the function is: // // ISLOGICAL(value) func (fn *formulaFuncs) ISLOGICAL(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISLOGICAL requires 1 argument") } val := argsList.Front().Value.(formulaArg).Value() if strings.EqualFold("TRUE", val) || strings.EqualFold("FALSE", val) { return newBoolFormulaArg(true) } return newBoolFormulaArg(false) } // ISNA function tests if an initial supplied expression (or value) returns // the Excel #N/A Error, and if so, returns TRUE; Otherwise the function // returns FALSE. The syntax of the function is: // // ISNA(value) func (fn *formulaFuncs) ISNA(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISNA requires 1 argument") } if token := argsList.Front().Value.(formulaArg); token.Type == ArgError && token.String == formulaErrorNA { return newBoolFormulaArg(true) } return newBoolFormulaArg(false) } // ISNONTEXT function tests if a supplied value is text. If not, the // function returns TRUE; If the supplied value is text, the function returns // FALSE. The syntax of the function is: // // ISNONTEXT(value) func (fn *formulaFuncs) ISNONTEXT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISNONTEXT requires 1 argument") } if argsList.Front().Value.(formulaArg).Type == ArgString { return newBoolFormulaArg(false) } return newBoolFormulaArg(true) } // ISNUMBER function tests if a supplied value is a number. If so, // the function returns TRUE; Otherwise it returns FALSE. The syntax of the // function is: // // ISNUMBER(value) func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument") } arg := argsList.Front().Value.(formulaArg) arg = fn.implicitIntersect(arg) if arg.Type == ArgNumber { return newBoolFormulaArg(true) } return newBoolFormulaArg(false) } // ISODD function tests if a supplied number (or numeric expression) evaluates // to an odd number, and if so, returns TRUE; Otherwise, the function returns // FALSE. The syntax of the function is: // // ISODD(value) func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISODD requires 1 argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if int(arg.Number) != int(arg.Number)/2*2 { return newBoolFormulaArg(true) } return newBoolFormulaArg(false) } // ISREF function tests if a supplied value is a reference. If so, the // function returns TRUE; Otherwise it returns FALSE. The syntax of the // function is: // // ISREF(value) func (fn *formulaFuncs) ISREF(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISREF requires 1 argument") } arg := argsList.Front().Value.(formulaArg) if arg.cellRanges != nil && arg.cellRanges.Len() > 0 || arg.cellRefs != nil && arg.cellRefs.Len() > 0 { return newBoolFormulaArg(true) } return newBoolFormulaArg(false) } // ISTEXT function tests if a supplied value is text, and if so, returns TRUE; // Otherwise, the function returns FALSE. The syntax of the function is: // // ISTEXT(value) func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument") } token := argsList.Front().Value.(formulaArg) if token.ToNumber().Type != ArgError { return newBoolFormulaArg(false) } return newBoolFormulaArg(token.Type == ArgString) } // N function converts data into a numeric value. The syntax of the function // is: // // N(value) func (fn *formulaFuncs) N(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "N requires 1 argument") } token, num := argsList.Front().Value.(formulaArg), 0.0 if token.Type == ArgError { return token } if arg := token.ToNumber(); arg.Type == ArgNumber { num = arg.Number } if token.Value() == "TRUE" { num = 1 } return newNumberFormulaArg(num) } // NA function returns the Excel #N/A error. This error message has the // meaning 'value not available' and is produced when an Excel Formula is // unable to find a value that it needs. The syntax of the function is: // // NA() func (fn *formulaFuncs) NA(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "NA accepts no arguments") } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // SHEET function returns the Sheet number for a specified reference. The // syntax of the function is: // // SHEET([value]) func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg { if argsList.Len() > 1 { return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts at most 1 argument") } if argsList.Len() == 0 { idx, _ := fn.f.GetSheetIndex(fn.sheet) return newNumberFormulaArg(float64(idx + 1)) } arg := argsList.Front().Value.(formulaArg) if sheetIdx, _ := fn.f.GetSheetIndex(arg.Value()); sheetIdx != -1 { return newNumberFormulaArg(float64(sheetIdx + 1)) } if arg.cellRanges != nil && arg.cellRanges.Len() > 0 { if sheetIdx, _ := fn.f.GetSheetIndex(arg.cellRanges.Front().Value.(cellRange).From.Sheet); sheetIdx != -1 { return newNumberFormulaArg(float64(sheetIdx + 1)) } } if arg.cellRefs != nil && arg.cellRefs.Len() > 0 { if sheetIdx, _ := fn.f.GetSheetIndex(arg.cellRefs.Front().Value.(cellRef).Sheet); sheetIdx != -1 { return newNumberFormulaArg(float64(sheetIdx + 1)) } } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // SHEETS function returns the number of sheets in a supplied reference. The // result includes sheets that are Visible, Hidden or Very Hidden. The syntax // of the function is: // // SHEETS([reference]) func (fn *formulaFuncs) SHEETS(argsList *list.List) formulaArg { if argsList.Len() > 1 { return newErrorFormulaArg(formulaErrorVALUE, "SHEETS accepts at most 1 argument") } if argsList.Len() == 0 { return newNumberFormulaArg(float64(len(fn.f.GetSheetList()))) } arg := argsList.Front().Value.(formulaArg) sheetMap := map[string]struct{}{} if arg.cellRanges != nil && arg.cellRanges.Len() > 0 { for rng := arg.cellRanges.Front(); rng != nil; rng = rng.Next() { sheetMap[rng.Value.(cellRange).From.Sheet] = struct{}{} } } if arg.cellRefs != nil && arg.cellRefs.Len() > 0 { for ref := arg.cellRefs.Front(); ref != nil; ref = ref.Next() { sheetMap[ref.Value.(cellRef).Sheet] = struct{}{} } } if len(sheetMap) > 0 { return newNumberFormulaArg(float64(len(sheetMap))) } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // TYPE function returns an integer that represents the value's data type. The // syntax of the function is: // // TYPE(value) func (fn *formulaFuncs) TYPE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TYPE requires 1 argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgError: return newNumberFormulaArg(16) case ArgMatrix: return newNumberFormulaArg(64) case ArgNumber, ArgEmpty: if token.Boolean { return newNumberFormulaArg(4) } return newNumberFormulaArg(1) default: return newNumberFormulaArg(2) } } // T function tests if a supplied value is text and if so, returns the // supplied text; Otherwise, the function returns an empty text string. The // syntax of the function is: // // T(value) func (fn *formulaFuncs) T(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "T requires 1 argument") } token := argsList.Front().Value.(formulaArg) if token.Type == ArgError { return token } if token.Type == ArgNumber { return newStringFormulaArg("") } return newStringFormulaArg(token.Value()) } // Logical Functions // AND function tests a number of supplied conditions and returns TRUE or // FALSE. The syntax of the function is: // // AND(logical_test1,[logical_test2],...) func (fn *formulaFuncs) AND(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "AND requires at least 1 argument") } if argsList.Len() > 30 { return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments") } and := true for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgUnknown: continue case ArgString: if token.String == "TRUE" { continue } if token.String == "FALSE" { return newStringFormulaArg(token.String) } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) case ArgNumber: and = and && token.Number != 0 case ArgMatrix: // TODO return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } return newBoolFormulaArg(and) } // FALSE function returns the logical value FALSE. The syntax of the // function is: // // FALSE() func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments") } return newBoolFormulaArg(false) } // IFERROR function receives two values (or expressions) and tests if the // first of these evaluates to an error. The syntax of the function is: // // IFERROR(value,value_if_error) func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments") } value := argsList.Front().Value.(formulaArg) if value.Type != ArgError { if value.Type == ArgEmpty { return newNumberFormulaArg(0) } return value } return argsList.Back().Value.(formulaArg) } // IFNA function tests if an initial supplied value (or expression) evaluates // to the Excel #N/A error. If so, the function returns a second supplied // value; Otherwise the function returns the first supplied value. The syntax // of the function is: // // IFNA(value,value_if_na) func (fn *formulaFuncs) IFNA(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "IFNA requires 2 arguments") } arg := argsList.Front().Value.(formulaArg) if arg.Type == ArgError && arg.String == formulaErrorNA { return argsList.Back().Value.(formulaArg) } return arg } // IFS function tests a number of supplied conditions and returns the result // corresponding to the first condition that evaluates to TRUE. If none of // the supplied conditions evaluate to TRUE, the function returns the #N/A // error. // // IFS(logical_test1,value_if_true1,[logical_test2,value_if_true2],...) func (fn *formulaFuncs) IFS(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "IFS requires at least 2 arguments") } for arg := argsList.Front(); arg != nil; arg = arg.Next() { if arg.Value.(formulaArg).ToBool().Number == 1 { return arg.Next().Value.(formulaArg) } arg = arg.Next() } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } // NOT function returns the opposite to a supplied logical value. The syntax // of the function is: // // NOT(logical) func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument") } token := argsList.Front().Value.(formulaArg) switch token.Type { case ArgString, ArgList: if strings.ToUpper(token.String) == "TRUE" { return newBoolFormulaArg(false) } if strings.ToUpper(token.String) == "FALSE" { return newBoolFormulaArg(true) } case ArgNumber: return newBoolFormulaArg(!(token.Number != 0)) case ArgError: return token } return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument") } // OR function tests a number of supplied conditions and returns either TRUE // or FALSE. The syntax of the function is: // // OR(logical_test1,[logical_test2],...) func (fn *formulaFuncs) OR(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "OR requires at least 1 argument") } if argsList.Len() > 30 { return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments") } var or bool for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgUnknown: continue case ArgString: if token.String == "FALSE" { continue } if token.String == "TRUE" { or = true continue } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) case ArgNumber: if or = token.Number != 0; or { return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or))) } case ArgMatrix: args := list.New() for _, arg := range token.ToList() { args.PushBack(arg) } return fn.OR(args) } } return newBoolFormulaArg(or) } // SWITCH function compares a number of supplied values to a supplied test // expression and returns a result corresponding to the first value that // matches the test expression. A default value can be supplied, to be // returned if none of the supplied values match the test expression. The // syntax of the function is: // // SWITCH(expression,value1,result1,[value2,result2],[value3,result3],...,[default]) func (fn *formulaFuncs) SWITCH(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "SWITCH requires at least 3 arguments") } target := argsList.Front().Value.(formulaArg) argCount := argsList.Len() - 1 switchCount := int(math.Floor(float64(argCount) / 2)) hasDefaultClause := argCount%2 != 0 result := newErrorFormulaArg(formulaErrorNA, formulaErrorNA) if hasDefaultClause { result = argsList.Back().Value.(formulaArg) } if switchCount > 0 { arg := argsList.Front() for i := 0; i < switchCount; i++ { arg = arg.Next() if target.Value() == arg.Value.(formulaArg).Value() { result = arg.Next().Value.(formulaArg) break } arg = arg.Next() } } return result } // TRUE function returns the logical value TRUE. The syntax of the function // is: // // TRUE() func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments") } return newBoolFormulaArg(true) } // calcXor checking if numeric cell exists and count it by given arguments // sequence for the formula function XOR. func calcXor(argsList *list.List) formulaArg { count, ok := 0, false for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { case ArgError: return token case ArgNumber: ok = true if token.Number != 0 { count++ } case ArgMatrix: for _, value := range token.ToList() { if num := value.ToNumber(); num.Type == ArgNumber { ok = true if num.Number != 0 { count++ } } } } } if !ok { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newBoolFormulaArg(count%2 != 0) } // XOR function returns the Exclusive Or logical operation for one or more // supplied conditions. I.e. the Xor function returns TRUE if an odd number // of the supplied conditions evaluate to TRUE, and FALSE otherwise. The // syntax of the function is: // // XOR(logical_test1,[logical_test2],...) func (fn *formulaFuncs) XOR(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "XOR requires at least 1 argument") } return calcXor(argsList) } // Date and Time Functions // DATE returns a date, from a user-supplied year, month and day. The syntax // of the function is: // // DATE(year,month,day) func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments") } year := argsList.Front().Value.(formulaArg).ToNumber() month := argsList.Front().Next().Value.(formulaArg).ToNumber() day := argsList.Back().Value.(formulaArg).ToNumber() if year.Type != ArgNumber || month.Type != ArgNumber || day.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments") } d := makeDate(int(year.Number), time.Month(month.Number), int(day.Number)) return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), d) + 1) } // calcDateDif is an implementation of the formula function DATEDIF, // calculation difference between two dates. func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 { ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5] switch unit { case "d": diff = endArg.Number - startArg.Number case "md": smMD := em if ed < sd { smMD-- } diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1 case "ym": diff = float64(em - sm) if ed < sd { diff-- } if diff < 0 { diff += 12 } case "yd": syYD := sy if em < sm || (em == sm && ed < sd) { syYD++ } s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed)) e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd)) diff = s - e } return diff } // DATEDIF function calculates the number of days, months, or years between // two dates. The syntax of the function is: // // DATEDIF(start_date,end_date,unit) func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF requires 3 number arguments") } startArg, endArg := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber() if startArg.Type != ArgNumber || endArg.Type != ArgNumber { return startArg } if startArg.Number > endArg.Number { return newErrorFormulaArg(formulaErrorNUM, "start_date > end_date") } if startArg.Number == endArg.Number { return newNumberFormulaArg(0) } unit := strings.ToLower(argsList.Back().Value.(formulaArg).Value()) startDate, endDate := timeFromExcelTime(startArg.Number, false), timeFromExcelTime(endArg.Number, false) sy, smm, sd := startDate.Date() ey, emm, ed := endDate.Date() sm, em, diff := int(smm), int(emm), 0.0 switch unit { case "y": diff = float64(ey - sy) if em < sm || (em == sm && ed < sd) { diff-- } case "m": yDiff := ey - sy mDiff := em - sm if ed < sd { mDiff-- } if mDiff < 0 { yDiff-- mDiff += 12 } diff = float64(yDiff*12 + mDiff) case "d", "md", "ym", "yd": diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg) default: return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit") } return newNumberFormulaArg(diff) } // isDateOnlyFmt check if the given string matches date-only format regular expressions. func isDateOnlyFmt(dateString string) bool { for _, df := range dateOnlyFormats { subMatch := df.FindStringSubmatch(dateString) if len(subMatch) > 1 { return true } } return false } // isTimeOnlyFmt check if the given string matches time-only format regular expressions. func isTimeOnlyFmt(timeString string) bool { for _, tf := range timeFormats { subMatch := tf.FindStringSubmatch(timeString) if len(subMatch) > 1 { return true } } return false } // strToTimePatternHandler1 parse and convert the given string in pattern // hh to the time. func strToTimePatternHandler1(subMatch []string) (h, m int, s float64, err error) { h, err = strconv.Atoi(subMatch[0]) return } // strToTimePatternHandler2 parse and convert the given string in pattern // hh:mm to the time. func strToTimePatternHandler2(subMatch []string) (h, m int, s float64, err error) { if h, err = strconv.Atoi(subMatch[0]); err != nil { return } m, err = strconv.Atoi(subMatch[2]) return } // strToTimePatternHandler3 parse and convert the given string in pattern // mm:ss to the time. func strToTimePatternHandler3(subMatch []string) (h, m int, s float64, err error) { if m, err = strconv.Atoi(subMatch[0]); err != nil { return } s, err = strconv.ParseFloat(subMatch[2], 64) return } // strToTimePatternHandler4 parse and convert the given string in pattern // hh:mm:ss to the time. func strToTimePatternHandler4(subMatch []string) (h, m int, s float64, err error) { if h, err = strconv.Atoi(subMatch[0]); err != nil { return } if m, err = strconv.Atoi(subMatch[2]); err != nil { return } s, err = strconv.ParseFloat(subMatch[4], 64) return } // strToTime parse and convert the given string to the time. func strToTime(str string) (int, int, float64, bool, bool, formulaArg) { var subMatch []string pattern := "" for key, tf := range timeFormats { subMatch = tf.FindStringSubmatch(str) if len(subMatch) > 1 { pattern = key break } } if pattern == "" { return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } dateIsEmpty := subMatch[1] == "" subMatch = subMatch[49:] var ( l = len(subMatch) last = subMatch[l-1] am = last == "am" pm = last == "pm" hours, minutes int seconds float64 err error ) if handler, ok := map[string]func(match []string) (int, int, float64, error){ "hh": strToTimePatternHandler1, "hh:mm": strToTimePatternHandler2, "mm:ss": strToTimePatternHandler3, "hh:mm:ss": strToTimePatternHandler4, }[pattern]; ok { if hours, minutes, seconds, err = handler(subMatch); err != nil { return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } if minutes >= 60 { return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if am || pm { if hours > 12 || seconds >= 60 { return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } else if hours == 12 { hours = 0 } } else if hours >= 24 || seconds >= 10000 { return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return hours, minutes, seconds, pm, dateIsEmpty, newEmptyFormulaArg() } // strToDatePatternHandler1 parse and convert the given string in pattern // mm/dd/yy to the date. func strToDatePatternHandler1(subMatch []string) (int, int, int, bool, error) { var year, month, day int var err error if month, err = strconv.Atoi(subMatch[1]); err != nil { return 0, 0, 0, false, err } if day, err = strconv.Atoi(subMatch[3]); err != nil { return 0, 0, 0, false, err } if year, err = strconv.Atoi(subMatch[5]); err != nil { return 0, 0, 0, false, err } if year < 0 || year > 9999 || (year > 99 && year < 1900) { return 0, 0, 0, false, ErrParameterInvalid } return formatYear(year), month, day, subMatch[8] == "", err } // strToDatePatternHandler2 parse and convert the given string in pattern mm // dd, yy to the date. func strToDatePatternHandler2(subMatch []string) (int, int, int, bool, error) { var year, month, day int var err error month = month2num[subMatch[1]] if day, err = strconv.Atoi(subMatch[14]); err != nil { return 0, 0, 0, false, err } if year, err = strconv.Atoi(subMatch[16]); err != nil { return 0, 0, 0, false, err } if year < 0 || year > 9999 || (year > 99 && year < 1900) { return 0, 0, 0, false, ErrParameterInvalid } return formatYear(year), month, day, subMatch[19] == "", err } // strToDatePatternHandler3 parse and convert the given string in pattern // yy-mm-dd to the date. func strToDatePatternHandler3(subMatch []string) (int, int, int, bool, error) { var year, month, day int v1, err := strconv.Atoi(subMatch[1]) if err != nil { return 0, 0, 0, false, err } v2, err := strconv.Atoi(subMatch[3]) if err != nil { return 0, 0, 0, false, err } v3, err := strconv.Atoi(subMatch[5]) if err != nil { return 0, 0, 0, false, err } if v1 >= 1900 && v1 < 10000 { year = v1 month = v2 day = v3 } else if v1 > 0 && v1 < 13 { month = v1 day = v2 year = v3 } else { return 0, 0, 0, false, ErrParameterInvalid } return year, month, day, subMatch[8] == "", err } // strToDatePatternHandler4 parse and convert the given string in pattern // yy-mmStr-dd, yy to the date. func strToDatePatternHandler4(subMatch []string) (int, int, int, bool, error) { var year, month, day int var err error if year, err = strconv.Atoi(subMatch[16]); err != nil { return 0, 0, 0, false, err } month = month2num[subMatch[3]] if day, err = strconv.Atoi(subMatch[1]); err != nil { return 0, 0, 0, false, err } return formatYear(year), month, day, subMatch[19] == "", err } // strToDate parse and convert the given string to the date. func strToDate(str string) (int, int, int, bool, formulaArg) { var subMatch []string pattern := "" for key, df := range dateFormats { subMatch = df.FindStringSubmatch(str) if len(subMatch) > 1 { pattern = key break } } if pattern == "" { return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } var ( timeIsEmpty bool year, month, day int err error ) if handler, ok := map[string]func(match []string) (int, int, int, bool, error){ "mm/dd/yy": strToDatePatternHandler1, "mm dd, yy": strToDatePatternHandler2, "yy-mm-dd": strToDatePatternHandler3, "yy-mmStr-dd": strToDatePatternHandler4, }[pattern]; ok { if year, month, day, timeIsEmpty, err = handler(subMatch); err != nil { return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } if !validateDate(year, month, day) { return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return year, month, day, timeIsEmpty, newEmptyFormulaArg() } // DATEVALUE function converts a text representation of a date into an Excel // date. For example, the function converts a text string representing a // date, into the serial number that represents the date in Excels' date-time // code. The syntax of the function is: // // DATEVALUE(date_text) func (fn *formulaFuncs) DATEVALUE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "DATEVALUE requires 1 argument") } dateText := argsList.Front().Value.(formulaArg).Value() if !isDateOnlyFmt(dateText) { if _, _, _, _, _, err := strToTime(dateText); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateText) if err.Type == ArgError { return err } return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1) } // DAY function returns the day of a date, represented by a serial number. The // day is given as an integer ranging from 1 to 31. The syntax of the // function is: // // DAY(serial_number) func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "DAY requires exactly 1 argument") } arg := argsList.Front().Value.(formulaArg) num := arg.ToNumber() if num.Type != ArgNumber { dateString := strings.ToLower(arg.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } _, _, day, _, err := strToDate(dateString) if err.Type == ArgError { return err } return newNumberFormulaArg(float64(day)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "DAY only accepts positive argument") } if num.Number <= 60 { return newNumberFormulaArg(math.Mod(num.Number, 31.0)) } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Day())) } // DAYS function returns the number of days between two supplied dates. The // syntax of the function is: // // DAYS(end_date,start_date) func (fn *formulaFuncs) DAYS(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "DAYS requires 2 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } end, start := args.List[0], args.List[1] return newNumberFormulaArg(end.Number - start.Number) } // DAYS360 function returns the number of days between 2 dates, based on a // 360-day year (12 x 30 months). The syntax of the function is: // // DAYS360(start_date,end_date,[method]) func (fn *formulaFuncs) DAYS360(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "DAYS360 requires at least 2 arguments") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "DAYS360 requires at most 3 arguments") } startDate := toExcelDateArg(argsList.Front().Value.(formulaArg)) if startDate.Type != ArgNumber { return startDate } endDate := toExcelDateArg(argsList.Front().Next().Value.(formulaArg)) if endDate.Type != ArgNumber { return endDate } start, end := timeFromExcelTime(startDate.Number, false), timeFromExcelTime(endDate.Number, false) sy, sm, sd, ey, em, ed := start.Year(), int(start.Month()), start.Day(), end.Year(), int(end.Month()), end.Day() method := newBoolFormulaArg(false) if argsList.Len() > 2 { if method = argsList.Back().Value.(formulaArg).ToBool(); method.Type != ArgNumber { return method } } if method.Number == 1 { if sd == 31 { sd-- } if ed == 31 { ed-- } } else { if getDaysInMonth(sy, sm) == sd { sd = 30 } if ed > 30 { if sd < 30 { em++ ed = 1 } else { ed = 30 } } } return newNumberFormulaArg(float64(360*(ey-sy) + 30*(em-sm) + (ed - sd))) } // ISOWEEKNUM function returns the ISO week number of a supplied date. The // syntax of the function is: // // ISOWEEKNUM(date) func (fn *formulaFuncs) ISOWEEKNUM(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ISOWEEKNUM requires 1 argument") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() weekNum := 0 if num.Type != ArgNumber { dateString := strings.ToLower(date.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } _, weekNum = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC).ISOWeek() } else { if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } _, weekNum = timeFromExcelTime(num.Number, false).ISOWeek() } return newNumberFormulaArg(float64(weekNum)) } // EDATE function returns a date that is a specified number of months before or // after a supplied start date. The syntax of function is: // // EDATE(start_date,months) func (fn *formulaFuncs) EDATE(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "EDATE requires 2 arguments") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() var dateTime time.Time if num.Type != ArgNumber { dateString := strings.ToLower(date.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } dateTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()) } else { if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } dateTime = timeFromExcelTime(num.Number, false) } month := argsList.Back().Value.(formulaArg).ToNumber() if month.Type != ArgNumber { return month } y, d := dateTime.Year(), dateTime.Day() m := int(dateTime.Month()) + int(month.Number) if month.Number < 0 { y -= int(math.Ceil(-1 * float64(m) / 12)) } if month.Number > 11 { y += int(math.Floor(float64(m) / 12)) } if m = m % 12; m < 0 { m += 12 } if d > 28 { if days := getDaysInMonth(y, m); d > days { d = days } } result, _ := timeToExcelTime(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC), false) return newNumberFormulaArg(result) } // EOMONTH function returns the last day of the month, that is a specified // number of months before or after an initial supplied start date. The syntax // of the function is: // // EOMONTH(start_date,months) func (fn *formulaFuncs) EOMONTH(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "EOMONTH requires 2 arguments") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() var dateTime time.Time if num.Type != ArgNumber { dateString := strings.ToLower(date.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } dateTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()) } else { if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } dateTime = timeFromExcelTime(num.Number, false) } months := argsList.Back().Value.(formulaArg).ToNumber() if months.Type != ArgNumber { return months } y, m := dateTime.Year(), int(dateTime.Month())+int(months.Number)-1 if m < 0 { y -= int(math.Ceil(-1 * float64(m) / 12)) } if m > 11 { y += int(math.Floor(float64(m) / 12)) } if m = m % 12; m < 0 { m += 12 } result, _ := timeToExcelTime(time.Date(y, time.Month(m+1), getDaysInMonth(y, m+1), 0, 0, 0, 0, time.UTC), false) return newNumberFormulaArg(result) } // HOUR function returns an integer representing the hour component of a // supplied Excel time. The syntax of the function is: // // HOUR(serial_number) func (fn *formulaFuncs) HOUR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "HOUR requires exactly 1 argument") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() if num.Type != ArgNumber { timeString := strings.ToLower(date.Value()) if !isTimeOnlyFmt(timeString) { _, _, _, _, err := strToDate(timeString) if err.Type == ArgError { return err } } h, _, _, pm, _, err := strToTime(timeString) if err.Type == ArgError { return err } if pm { h += 12 } return newNumberFormulaArg(float64(h)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "HOUR only accepts positive argument") } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Hour())) } // MINUTE function returns an integer representing the minute component of a // supplied Excel time. The syntax of the function is: // // MINUTE(serial_number) func (fn *formulaFuncs) MINUTE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "MINUTE requires exactly 1 argument") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() if num.Type != ArgNumber { timeString := strings.ToLower(date.Value()) if !isTimeOnlyFmt(timeString) { _, _, _, _, err := strToDate(timeString) if err.Type == ArgError { return err } } _, m, _, _, _, err := strToTime(timeString) if err.Type == ArgError { return err } return newNumberFormulaArg(float64(m)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "MINUTE only accepts positive argument") } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Minute())) } // MONTH function returns the month of a date represented by a serial number. // The month is given as an integer, ranging from 1 (January) to 12 // (December). The syntax of the function is: // // MONTH(serial_number) func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "MONTH requires exactly 1 argument") } arg := argsList.Front().Value.(formulaArg) num := arg.ToNumber() if num.Type != ArgNumber { dateString := strings.ToLower(arg.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } _, month, _, _, err := strToDate(dateString) if err.Type == ArgError { return err } return newNumberFormulaArg(float64(month)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "MONTH only accepts positive argument") } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Month())) } // genWeekendMask generate weekend mask of a series of seven 0's and 1's which // represent the seven weekdays, starting from Monday. func genWeekendMask(weekend int) []byte { if masks, ok := map[int][]int{ 1: {5, 6}, 2: {6, 0}, 3: {0, 1}, 4: {1, 2}, 5: {2, 3}, 6: {3, 4}, 7: {4, 5}, 11: {6}, 12: {0}, 13: {1}, 14: {2}, 15: {3}, 16: {4}, 17: {5}, }[weekend]; ok { mask := make([]byte, 7) for _, idx := range masks { mask[idx] = 1 } return mask } return nil } // isWorkday check if the date is workday. func isWorkday(weekendMask []byte, date float64) bool { dateTime := timeFromExcelTime(date, false) weekday := dateTime.Weekday() if weekday == time.Sunday { weekday = 7 } return weekendMask[weekday-1] == 0 } // prepareWorkday returns weekend mask and workdays pre week by given days // counted as weekend. func prepareWorkday(weekend formulaArg) ([]byte, int) { weekendArg := weekend.ToNumber() if weekendArg.Type != ArgNumber { return nil, 0 } var weekendMask []byte var workdaysPerWeek int if len(weekend.Value()) == 7 { // possible string values for the weekend argument for _, mask := range weekend.Value() { if mask != '0' && mask != '1' { return nil, 0 } weekendMask = append(weekendMask, byte(mask)-48) } } else { weekendMask = genWeekendMask(int(weekendArg.Number)) } for _, mask := range weekendMask { if mask == 0 { workdaysPerWeek++ } } return weekendMask, workdaysPerWeek } // toExcelDateArg function converts a text representation of a time, into an // Excel date time number formula argument. func toExcelDateArg(arg formulaArg) formulaArg { num := arg.ToNumber() if num.Type != ArgNumber { dateString := strings.ToLower(arg.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } num.Number, _ = timeToExcelTime(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC), false) return newNumberFormulaArg(num.Number) } if arg.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return num } // prepareHolidays function converts array type formula arguments to into an // Excel date time number formula arguments list. func prepareHolidays(args formulaArg) []int { var holidays []int for _, arg := range args.ToList() { num := toExcelDateArg(arg) if num.Type != ArgNumber { continue } holidays = append(holidays, int(math.Ceil(num.Number))) } return holidays } // workdayIntl is an implementation of the formula function WORKDAY.INTL. func workdayIntl(endDate, sign int, holidays []int, weekendMask []byte, startDate float64) int { for i := 0; i < len(holidays); i++ { holiday := holidays[i] if sign > 0 { if holiday > endDate { break } } else { if holiday < endDate { break } } if sign > 0 { if holiday > int(math.Ceil(startDate)) { if isWorkday(weekendMask, float64(holiday)) { endDate += sign for !isWorkday(weekendMask, float64(endDate)) { endDate += sign } } } } else { if holiday < int(math.Ceil(startDate)) { if isWorkday(weekendMask, float64(holiday)) { endDate += sign for !isWorkday(weekendMask, float64(endDate)) { endDate += sign } } } } } return endDate } // NETWORKDAYS function calculates the number of work days between two supplied // dates (including the start and end date). The calculation includes all // weekdays (Mon - Fri), excluding a supplied list of holidays. The syntax of // the function is: // // NETWORKDAYS(start_date,end_date,[holidays]) func (fn *formulaFuncs) NETWORKDAYS(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS requires at least 2 arguments") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS requires at most 3 arguments") } args := list.New() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(argsList.Front().Next().Value.(formulaArg)) args.PushBack(newNumberFormulaArg(1)) if argsList.Len() == 3 { args.PushBack(argsList.Back().Value.(formulaArg)) } return fn.NETWORKDAYSdotINTL(args) } // NETWORKDAYSdotINTL function calculates the number of whole work days between // two supplied dates, excluding weekends and holidays. The function allows // the user to specify which days are counted as weekends and holidays. The // syntax of the function is: // // NETWORKDAYS.INTL(start_date,end_date,[weekend],[holidays]) func (fn *formulaFuncs) NETWORKDAYSdotINTL(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS.INTL requires at least 2 arguments") } if argsList.Len() > 4 { return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS.INTL requires at most 4 arguments") } startDate := toExcelDateArg(argsList.Front().Value.(formulaArg)) if startDate.Type != ArgNumber { return startDate } endDate := toExcelDateArg(argsList.Front().Next().Value.(formulaArg)) if endDate.Type != ArgNumber { return endDate } weekend := newNumberFormulaArg(1) if argsList.Len() > 2 { weekend = argsList.Front().Next().Next().Value.(formulaArg) } var holidays []int if argsList.Len() == 4 { holidays = prepareHolidays(argsList.Back().Value.(formulaArg)) sort.Ints(holidays) } weekendMask, workdaysPerWeek := prepareWorkday(weekend) if workdaysPerWeek == 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } sign := 1 if startDate.Number > endDate.Number { sign = -1 startDate.Number, endDate.Number = endDate.Number, startDate.Number } offset := endDate.Number - startDate.Number count := int(math.Floor(offset/7) * float64(workdaysPerWeek)) daysMod := int(offset) % 7 for daysMod >= 0 { if isWorkday(weekendMask, endDate.Number-float64(daysMod)) { count++ } daysMod-- } for i := 0; i < len(holidays); i++ { holiday := float64(holidays[i]) if isWorkday(weekendMask, holiday) && holiday >= startDate.Number && holiday <= endDate.Number { count-- } } return newNumberFormulaArg(float64(sign * count)) } // WORKDAY function returns a date that is a supplied number of working days // (excluding weekends and holidays) ahead of a given start date. The syntax // of the function is: // // WORKDAY(start_date,days,[holidays]) func (fn *formulaFuncs) WORKDAY(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY requires at least 2 arguments") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY requires at most 3 arguments") } args := list.New() args.PushBack(argsList.Front().Value.(formulaArg)) args.PushBack(argsList.Front().Next().Value.(formulaArg)) args.PushBack(newNumberFormulaArg(1)) if argsList.Len() == 3 { args.PushBack(argsList.Back().Value.(formulaArg)) } return fn.WORKDAYdotINTL(args) } // WORKDAYdotINTL function returns a date that is a supplied number of working // days (excluding weekends and holidays) ahead of a given start date. The // function allows the user to specify which days of the week are counted as // weekends. The syntax of the function is: // // WORKDAY.INTL(start_date,days,[weekend],[holidays]) func (fn *formulaFuncs) WORKDAYdotINTL(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at least 2 arguments") } if argsList.Len() > 4 { return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at most 4 arguments") } startDate := toExcelDateArg(argsList.Front().Value.(formulaArg)) if startDate.Type != ArgNumber { return startDate } days := argsList.Front().Next().Value.(formulaArg).ToNumber() if days.Type != ArgNumber { return days } weekend := newNumberFormulaArg(1) if argsList.Len() > 2 { weekend = argsList.Front().Next().Next().Value.(formulaArg) } var holidays []int if argsList.Len() == 4 { holidays = prepareHolidays(argsList.Back().Value.(formulaArg)) sort.Ints(holidays) } if days.Number == 0 { return newNumberFormulaArg(math.Ceil(startDate.Number)) } weekendMask, workdaysPerWeek := prepareWorkday(weekend) if workdaysPerWeek == 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } sign := 1 if days.Number < 0 { sign = -1 } offset := int(days.Number) / workdaysPerWeek daysMod := int(days.Number) % workdaysPerWeek endDate := int(math.Ceil(startDate.Number)) + offset*7 if daysMod == 0 { for !isWorkday(weekendMask, float64(endDate)) { endDate -= sign } } else { for daysMod != 0 { endDate += sign if isWorkday(weekendMask, float64(endDate)) { if daysMod < 0 { daysMod++ continue } daysMod-- } } } return newNumberFormulaArg(float64(workdayIntl(endDate, sign, holidays, weekendMask, startDate.Number))) } // YEAR function returns an integer representing the year of a supplied date. // The syntax of the function is: // // YEAR(serial_number) func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "YEAR requires exactly 1 argument") } arg := argsList.Front().Value.(formulaArg) num := arg.ToNumber() if num.Type != ArgNumber { dateString := strings.ToLower(arg.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } year, _, _, _, err := strToDate(dateString) if err.Type == ArgError { return err } return newNumberFormulaArg(float64(year)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "YEAR only accepts positive argument") } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Year())) } // yearFracBasisCond is an implementation of the yearFracBasis1. func yearFracBasisCond(sy, sm, sd, ey, em, ed int) bool { return (isLeapYear(sy) && (sm < 2 || (sm == 2 && sd <= 29))) || (isLeapYear(ey) && (em > 2 || (em == 2 && ed == 29))) } // yearFracBasis0 function returns the fraction of a year that between two // supplied dates in US (NASD) 30/360 type of day. func yearFracBasis0(startDate, endDate float64) (dayDiff, daysInYear float64) { startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) sy, smM, sd := startTime.Date() ey, emM, ed := endTime.Date() sm, em := int(smM), int(emM) if sd == 31 { sd-- } if sd == 30 && ed == 31 { ed-- } else if leap := isLeapYear(sy); sm == 2 && ((leap && sd == 29) || (!leap && sd == 28)) { sd = 30 if leap := isLeapYear(ey); em == 2 && ((leap && ed == 29) || (!leap && ed == 28)) { ed = 30 } } dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) daysInYear = 360 return } // yearFracBasis1 function returns the fraction of a year that between two // supplied dates in actual type of day. func yearFracBasis1(startDate, endDate float64) (dayDiff, daysInYear float64) { startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) sy, smM, sd := startTime.Date() ey, emM, ed := endTime.Date() sm, em := int(smM), int(emM) dayDiff = endDate - startDate isYearDifferent := sy != ey if isYearDifferent && (ey != sy+1 || sm < em || (sm == em && sd < ed)) { dayCount := 0 for y := sy; y <= ey; y++ { dayCount += getYearDays(y, 1) } daysInYear = float64(dayCount) / float64(ey-sy+1) } else { if !isYearDifferent && isLeapYear(sy) { daysInYear = 366 } else { if isYearDifferent && yearFracBasisCond(sy, sm, sd, ey, em, ed) { daysInYear = 366 } else { daysInYear = 365 } } } return } // yearFracBasis4 function returns the fraction of a year that between two // supplied dates in European 30/360 type of day. func yearFracBasis4(startDate, endDate float64) (dayDiff, daysInYear float64) { startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) sy, smM, sd := startTime.Date() ey, emM, ed := endTime.Date() sm, em := int(smM), int(emM) if sd == 31 { sd-- } if ed == 31 { ed-- } dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) daysInYear = 360 return } // yearFrac is an implementation of the formula function YEARFRAC. func yearFrac(startDate, endDate float64, basis int) formulaArg { startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) if startTime.Equal(endTime) { return newNumberFormulaArg(0) } var dayDiff, daysInYear float64 switch basis { case 0: dayDiff, daysInYear = yearFracBasis0(startDate, endDate) case 1: dayDiff, daysInYear = yearFracBasis1(startDate, endDate) case 2: dayDiff = endDate - startDate daysInYear = 360 case 3: dayDiff = endDate - startDate daysInYear = 365 case 4: dayDiff, daysInYear = yearFracBasis4(startDate, endDate) default: return newErrorFormulaArg(formulaErrorNUM, "invalid basis") } return newNumberFormulaArg(dayDiff / daysInYear) } // getYearDays return days of the year with specifying the type of day count // basis to be used. func getYearDays(year, basis int) int { switch basis { case 1: if isLeapYear(year) { return 366 } return 365 case 3: return 365 default: return 360 } } // YEARFRAC function returns the fraction of a year that is represented by the // number of whole days between two supplied dates. The syntax of the // function is: // // YEARFRAC(start_date,end_date,[basis]) func (fn *formulaFuncs) YEARFRAC(argsList *list.List) formulaArg { if argsList.Len() != 2 && argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "YEARFRAC requires 3 or 4 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } start, end := args.List[0], args.List[1] basis := newNumberFormulaArg(0) if argsList.Len() == 3 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return basis } } return yearFrac(start.Number, end.Number, int(basis.Number)) } // NOW function returns the current date and time. The function receives no // arguments and therefore. The syntax of the function is: // // NOW() func (fn *formulaFuncs) NOW(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "NOW accepts no arguments") } now := time.Now() _, offset := now.Zone() return newNumberFormulaArg(25569.0 + float64(now.Unix()+int64(offset))/86400) } // SECOND function returns an integer representing the second component of a // supplied Excel time. The syntax of the function is: // // SECOND(serial_number) func (fn *formulaFuncs) SECOND(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "SECOND requires exactly 1 argument") } date := argsList.Front().Value.(formulaArg) num := date.ToNumber() if num.Type != ArgNumber { timeString := strings.ToLower(date.Value()) if !isTimeOnlyFmt(timeString) { _, _, _, _, err := strToDate(timeString) if err.Type == ArgError { return err } } _, _, s, _, _, err := strToTime(timeString) if err.Type == ArgError { return err } return newNumberFormulaArg(float64(int(s) % 60)) } if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "SECOND only accepts positive argument") } return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Second())) } // TIME function accepts three integer arguments representing hours, minutes // and seconds, and returns an Excel time. I.e. the function returns the // decimal value that represents the time in Excel. The syntax of the // function is: // // TIME(hour,minute,second) func (fn *formulaFuncs) TIME(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments") } h := argsList.Front().Value.(formulaArg).ToNumber() m := argsList.Front().Next().Value.(formulaArg).ToNumber() s := argsList.Back().Value.(formulaArg).ToNumber() if h.Type != ArgNumber || m.Type != ArgNumber || s.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments") } t := (h.Number*3600 + m.Number*60 + s.Number) / 86400 if t < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(t) } // TIMEVALUE function converts a text representation of a time, into an Excel // time. The syntax of the function is: // // TIMEVALUE(time_text) func (fn *formulaFuncs) TIMEVALUE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TIMEVALUE requires exactly 1 argument") } date := argsList.Front().Value.(formulaArg) timeString := strings.ToLower(date.Value()) if !isTimeOnlyFmt(timeString) { _, _, _, _, err := strToDate(timeString) if err.Type == ArgError { return err } } h, m, s, pm, _, err := strToTime(timeString) if err.Type == ArgError { return err } if pm { h += 12 } args := list.New() args.PushBack(newNumberFormulaArg(float64(h))) args.PushBack(newNumberFormulaArg(float64(m))) args.PushBack(newNumberFormulaArg(s)) return fn.TIME(args) } // TODAY function returns the current date. The function has no arguments and // therefore. The syntax of the function is: // // TODAY() func (fn *formulaFuncs) TODAY(argsList *list.List) formulaArg { if argsList.Len() != 0 { return newErrorFormulaArg(formulaErrorVALUE, "TODAY accepts no arguments") } now := time.Now() _, offset := now.Zone() return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), now.Unix()+int64(offset)) + 1) } // makeDate return date as a Unix time, the number of seconds elapsed since // January 1, 1970 UTC. func makeDate(y int, m time.Month, d int) int64 { if y == 1900 && int(m) <= 2 { d-- } date := time.Date(y, m, d, 0, 0, 0, 0, time.UTC) return date.Unix() } // daysBetween return time interval of the given start timestamp and end // timestamp. func daysBetween(startDate, endDate int64) float64 { return float64(int(0.5 + float64((endDate-startDate)/86400))) } // WEEKDAY function returns an integer representing the day of the week for a // supplied date. The syntax of the function is: // // WEEKDAY(serial_number,[return_type]) func (fn *formulaFuncs) WEEKDAY(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY allows at most 2 arguments") } sn := argsList.Front().Value.(formulaArg) num := sn.ToNumber() weekday, returnType := 0, 1 if num.Type != ArgNumber { dateString := strings.ToLower(sn.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } weekday = int(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()).Weekday()) } else { if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } weekday = int(timeFromExcelTime(num.Number, false).Weekday()) } if argsList.Len() == 2 { returnTypeArg := argsList.Back().Value.(formulaArg).ToNumber() if returnTypeArg.Type != ArgNumber { return returnTypeArg } returnType = int(returnTypeArg.Number) } if returnType == 2 { returnType = 11 } weekday++ if returnType == 1 { return newNumberFormulaArg(float64(weekday)) } if returnType == 3 { return newNumberFormulaArg(float64((weekday + 6 - 1) % 7)) } if returnType >= 11 && returnType <= 17 { return newNumberFormulaArg(float64((weekday+6-(returnType-10))%7 + 1)) } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } // weeknum is an implementation of the formula function WEEKNUM. func (fn *formulaFuncs) weeknum(snTime time.Time, returnType int) formulaArg { days := snTime.YearDay() weekMod, weekNum := days%7, math.Ceil(float64(days)/7) if weekMod == 0 { weekMod = 7 } year := snTime.Year() firstWeekday := int(time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC).Weekday()) var offset int switch returnType { case 1, 17: offset = 0 case 2, 11, 21: offset = 1 case 12, 13, 14, 15, 16: offset = returnType - 10 default: return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } padding := offset + 7 - firstWeekday if padding > 7 { padding -= 7 } if weekMod > padding { weekNum++ } if returnType == 21 && (firstWeekday == 0 || firstWeekday > 4) { if weekNum--; weekNum < 1 { if weekNum = 52; int(time.Date(year-1, time.January, 1, 0, 0, 0, 0, time.UTC).Weekday()) < 4 { weekNum++ } } } return newNumberFormulaArg(weekNum) } // WEEKNUM function returns an integer representing the week number (from 1 to // 53) of the year. The syntax of the function is: // // WEEKNUM(serial_number,[return_type]) func (fn *formulaFuncs) WEEKNUM(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "WEEKNUM requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "WEEKNUM allows at most 2 arguments") } sn := argsList.Front().Value.(formulaArg) num, returnType := sn.ToNumber(), 1 var snTime time.Time if num.Type != ArgNumber { dateString := strings.ToLower(sn.Value()) if !isDateOnlyFmt(dateString) { if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { return err } } y, m, d, _, err := strToDate(dateString) if err.Type == ArgError { return err } snTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()) } else { if num.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } snTime = timeFromExcelTime(num.Number, false) } if argsList.Len() == 2 { returnTypeArg := argsList.Back().Value.(formulaArg).ToNumber() if returnTypeArg.Type != ArgNumber { return returnTypeArg } returnType = int(returnTypeArg.Number) } return fn.weeknum(snTime, returnType) } // Text Functions // prepareToText checking and prepare arguments for the formula functions // ARRAYTOTEXT and VALUETOTEXT. func prepareToText(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) } format := newNumberFormulaArg(0) if argsList.Len() == 2 { if format = argsList.Back().Value.(formulaArg).ToNumber(); format.Type != ArgNumber { return format } } if format.Number != 0 && format.Number != 1 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return format } // ARRAYTOTEXT function returns an array of text values from any specified // range. It passes text values unchanged, and converts non-text values to // text. The syntax of the function is: // // ARRAYTOTEXT(array,[format]) func (fn *formulaFuncs) ARRAYTOTEXT(argsList *list.List) formulaArg { var mtx [][]string format := prepareToText("ARRAYTOTEXT", argsList) if format.Type != ArgNumber { return format } for _, rows := range argsList.Front().Value.(formulaArg).Matrix { var row []string for _, cell := range rows { if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 { row = append(row, fmt.Sprintf("\"%s\"", cell.Value())) continue } row = append(row, cell.Value()) } mtx = append(mtx, row) } var text []string for _, row := range mtx { if format.Number == 1 { text = append(text, strings.Join(row, ",")) continue } text = append(text, strings.Join(row, ", ")) } if format.Number == 1 { return newStringFormulaArg(fmt.Sprintf("{%s}", strings.Join(text, ";"))) } return newStringFormulaArg(strings.Join(text, ", ")) } // bahttextAppendDigit appends a digit to the passed string. func bahttextAppendDigit(text string, digit int) string { if 0 <= digit && digit <= 9 { return text + []string{th0, th1, th2, th3, th4, th5, th6, th7, th8, th9}[digit] } return text } // bahttextAppendPow10 appends a value raised to a power of 10: digit*10^pow10. func bahttextAppendPow10(text string, digit, pow10 int) string { text = bahttextAppendDigit(text, digit) switch pow10 { case 2: text += th1e2 case 3: text += th1e3 case 4: text += th1e4 case 5: text += th1e5 } return text } // bahttextAppendBlock appends a block of 6 digits to the passed string. func bahttextAppendBlock(text string, val int) string { if val >= 100000 { text = bahttextAppendPow10(text, val/100000, 5) val %= 100000 } if val >= 10000 { text = bahttextAppendPow10(text, val/10000, 4) val %= 10000 } if val >= 1000 { text = bahttextAppendPow10(text, val/1000, 3) val %= 1000 } if val >= 100 { text = bahttextAppendPow10(text, val/100, 2) val %= 100 } if val > 0 { n10 := val / 10 n1 := val % 10 if n10 >= 1 { if n10 >= 3 { text = bahttextAppendDigit(text, n10) } else if n10 == 2 { text += th20 } text += th10 } if n10 > 0 && n1 == 1 { text += th11 } else if n1 > 0 { text = bahttextAppendDigit(text, n1) } } return text } // BAHTTEXT function converts a number into Thai text, with the suffix "Baht". // The syntax of the function is: // // BAHTTEXT(number) func (fn *formulaFuncs) BAHTTEXT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "BAHTTEXT requires 1 numeric argument") } token := argsList.Front().Value.(formulaArg) number := token.ToNumber() if number.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, number.Error) } minus := number.Number < 0 num := math.Floor(math.Abs(number.Number)*100 + 0.5) // split baht and satang splitBlock := func(val, size float64) (float64, int) { integer, frac := math.Modf((val + 0.1) / size) frac = frac*size + 0.1 return integer, int(frac) } baht, satang := splitBlock(num, 100) var text string if baht == 0 { if satang == 0 { text += th0 } } else { for baht > 0 { var block string var nBlock int baht, nBlock = splitBlock(baht, 1.0e6) if nBlock > 0 { block = bahttextAppendBlock(block, nBlock) } if baht > 0 { block = th1e6 + block } text = block + text } } if len(text) > 0 { text += thBaht } if satang == 0 { text += thDot0 } else { text = bahttextAppendBlock(text, satang) text += thSatang } if minus { text = thMinus + text } return newStringFormulaArg(text) } // CHAR function returns the character relating to a supplied character set // number (from 1 to 255). The syntax of the function is: // // CHAR(number) func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "CHAR requires 1 argument") } arg := argsList.Front().Value.(formulaArg).ToNumber() if arg.Type != ArgNumber { return arg } num := int(arg.Number) if num < 0 || num > MaxFieldLength { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newStringFormulaArg(fmt.Sprintf("%c", num)) } // CLEAN removes all non-printable characters from a supplied text string. The // syntax of the function is: // // CLEAN(text) func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument") } b := bytes.Buffer{} for _, c := range argsList.Front().Value.(formulaArg).Value() { if c > 31 { b.WriteRune(c) } } return newStringFormulaArg(b.String()) } // CODE function converts the first character of a supplied text string into // the associated numeric character set code used by your computer. The // syntax of the function is: // // CODE(text) func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg { return fn.code("CODE", argsList) } // code is an implementation of the formula functions CODE and UNICODE. func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name)) } text := argsList.Front().Value.(formulaArg).Value() if len(text) == 0 { if name == "CODE" { return newNumberFormulaArg(0) } return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newNumberFormulaArg(float64(text[0])) } // CONCAT function joins together a series of supplied text strings into one // combined text string. // // CONCAT(text1,[text2],...) func (fn *formulaFuncs) CONCAT(argsList *list.List) formulaArg { return fn.concat(argsList) } // CONCATENATE function joins together a series of supplied text strings into // one combined text string. // // CONCATENATE(text1,[text2],...) func (fn *formulaFuncs) CONCATENATE(argsList *list.List) formulaArg { return fn.concat(argsList) } // concat is an implementation of the formula functions CONCAT and // CONCATENATE. func (fn *formulaFuncs) concat(argsList *list.List) formulaArg { var buf bytes.Buffer for arg := argsList.Front(); arg != nil; arg = arg.Next() { for _, cell := range arg.Value.(formulaArg).ToList() { if cell.Type == ArgError { return cell } buf.WriteString(cell.Value()) } } return newStringFormulaArg(buf.String()) } // DBCS converts half-width (single-byte) letters within a character string to // full-width (double-byte) characters. The syntax of the function is: // // DBCS(text) func (fn *formulaFuncs) DBCS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "DBCS requires 1 argument") } arg := argsList.Front().Value.(formulaArg) if arg.Type == ArgError { return arg } if fn.f.options.CultureInfo == CultureNameJaJP || fn.f.options.CultureInfo == CultureNameZhCN || fn.f.options.CultureInfo == CultureNameZhTW { var chars []string for _, r := range arg.Value() { code := r if code == 32 { code = 12288 } else { code += 65248 } if (code < 32 || code > 126) && r != 165 && code < 65381 { chars = append(chars, string(code)) } else { chars = append(chars, string(r)) } } return newStringFormulaArg(strings.Join(chars, "")) } return arg } // EXACT function tests if two supplied text strings or values are exactly // equal and if so, returns TRUE; Otherwise, the function returns FALSE. The // function is case-sensitive. The syntax of the function is: // // EXACT(text1,text2) func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments") } text1 := argsList.Front().Value.(formulaArg).Value() text2 := argsList.Back().Value.(formulaArg).Value() return newBoolFormulaArg(text1 == text2) } // FIXED function rounds a supplied number to a specified number of decimal // places and then converts this into text. The syntax of the function is: // // FIXED(number,[decimals],[no_commas]) func (fn *formulaFuncs) FIXED(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "FIXED requires at least 1 argument") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "FIXED allows at most 3 arguments") } numArg := argsList.Front().Value.(formulaArg).ToNumber() if numArg.Type != ArgNumber { return numArg } precision, decimals, noCommas := 0, 0, false s := strings.Split(argsList.Front().Value.(formulaArg).Value(), ".") if argsList.Len() == 1 && len(s) == 2 { precision = len(s[1]) decimals = len(s[1]) } if argsList.Len() >= 2 { decimalsArg := argsList.Front().Next().Value.(formulaArg).ToNumber() if decimalsArg.Type != ArgNumber { return decimalsArg } decimals = int(decimalsArg.Number) } if argsList.Len() == 3 { noCommasArg := argsList.Back().Value.(formulaArg).ToBool() if noCommasArg.Type == ArgError { return noCommasArg } noCommas = noCommasArg.Boolean } n := math.Pow(10, float64(decimals)) r := numArg.Number * n fixed := float64(int(r+math.Copysign(0.5, r))) / n if decimals > 0 { precision = decimals } if noCommas { return newStringFormulaArg(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) } p := message.NewPrinter(language.English) return newStringFormulaArg(p.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) } // FIND function returns the position of a specified character or sub-string // within a supplied text string. The function is case-sensitive. The syntax // of the function is: // // FIND(find_text,within_text,[start_num]) func (fn *formulaFuncs) FIND(argsList *list.List) formulaArg { return fn.find("FIND", argsList) } // FINDB counts each double-byte character as 2 when you have enabled the // editing of a language that supports DBCS and then set it as the default // language. Otherwise, FINDB counts each character as 1. The syntax of the // function is: // // FINDB(find_text,within_text,[start_num]) func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg { return fn.find("FINDB", argsList) } // prepareFindArgs checking and prepare arguments for the formula functions // FIND, FINDB, SEARCH and SEARCHB. func (fn *formulaFuncs) prepareFindArgs(name string, argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name)) } startNum := 1 if argsList.Len() == 3 { numArg := argsList.Back().Value.(formulaArg).ToNumber() if numArg.Type != ArgNumber { return numArg } if numArg.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } startNum = int(numArg.Number) } return newListFormulaArg([]formulaArg{newNumberFormulaArg(float64(startNum))}) } // find is an implementation of the formula functions FIND, FINDB, SEARCH and // SEARCHB. func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg { args := fn.prepareFindArgs(name, argsList) if args.Type != ArgList { return args } findTextArg := argsList.Front().Value.(formulaArg) withinText := argsList.Front().Next().Value.(formulaArg).Value() startNum := int(args.List[0].Number) dbcs, search := name == "FINDB" || name == "SEARCHB", name == "SEARCH" || name == "SEARCHB" find := func(findText string) formulaArg { if findText == "" { return newNumberFormulaArg(float64(startNum)) } if search { findText, withinText = strings.ToUpper(findText), strings.ToUpper(withinText) } offset, ok := matchPattern(findText, withinText, dbcs, startNum) if !ok { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } result := offset if dbcs { var pre int for idx := range withinText { if pre > offset { break } if idx-pre > 1 { result++ } pre = idx } } return newNumberFormulaArg(float64(result)) } if findTextArg.Type == ArgMatrix { var mtx [][]formulaArg for _, row := range findTextArg.Matrix { var array []formulaArg for _, findText := range row { array = append(array, find(findText.Value())) } mtx = append(mtx, array) } return newMatrixFormulaArg(mtx) } return find(findTextArg.Value()) } // LEFT function returns a specified number of characters from the start of a // supplied text string. The syntax of the function is: // // LEFT(text,[num_chars]) func (fn *formulaFuncs) LEFT(argsList *list.List) formulaArg { return fn.leftRight("LEFT", argsList) } // LEFTB returns the first character or characters in a text string, based on // the number of bytes you specify. The syntax of the function is: // // LEFTB(text,[num_bytes]) func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg { return fn.leftRight("LEFTB", argsList) } // leftRight is an implementation of the formula functions LEFT, LEFTB, RIGHT, // RIGHTB. func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) } text, numChars := argsList.Front().Value.(formulaArg).Value(), 1 if argsList.Len() == 2 { numArg := argsList.Back().Value.(formulaArg).ToNumber() if numArg.Type != ArgNumber { return numArg } if numArg.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } numChars = int(numArg.Number) } if name == "LEFTB" || name == "RIGHTB" { if len(text) > numChars { if name == "LEFTB" { return newStringFormulaArg(text[:numChars]) } // RIGHTB return newStringFormulaArg(text[len(text)-numChars:]) } return newStringFormulaArg(text) } // LEFT/RIGHT if countUTF16String(text) > numChars { if name == "LEFT" { return newStringFormulaArg(truncateUTF16Units(text, numChars)) } // RIGHT return newStringFormulaArg(string([]rune(text)[utf8.RuneCountInString(text)-numChars:])) } return newStringFormulaArg(text) } // LEN returns the length of a supplied text string. The syntax of the // function is: // // LEN(text) func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument") } return newNumberFormulaArg(float64(countUTF16String(argsList.Front().Value.(formulaArg).Value()))) } // LENB returns the number of bytes used to represent the characters in a text // string. LENB counts 2 bytes per character only when a DBCS language is set // as the default language. Otherwise LENB behaves the same as LEN, counting // 1 byte per character. The syntax of the function is: // // LENB(text) func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument") } result := 0 for _, r := range argsList.Front().Value.(formulaArg).Value() { b := utf8.RuneLen(r) if b == 1 { result++ } else if b > 1 { result += 2 } } return newNumberFormulaArg(float64(result)) } // LOWER converts all characters in a supplied text string to lower case. The // syntax of the function is: // // LOWER(text) func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument") } return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).Value())) } // MID function returns a specified number of characters from the middle of a // supplied text string. The syntax of the function is: // // MID(text,start_num,num_chars) func (fn *formulaFuncs) MID(argsList *list.List) formulaArg { return fn.mid("MID", argsList) } // MIDB returns a specific number of characters from a text string, starting // at the position you specify, based on the number of bytes you specify. The // syntax of the function is: // // MID(text,start_num,num_chars) func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg { return fn.mid("MIDB", argsList) } // mid is an implementation of the formula functions MID and MIDB. func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name)) } text := argsList.Front().Value.(formulaArg).Value() startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if startNumArg.Type != ArgNumber { return startNumArg } if numCharsArg.Type != ArgNumber { return numCharsArg } startNum := int(startNumArg.Number) if startNum < 1 || numCharsArg.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if name == "MIDB" { var result string var cnt, offset int for _, char := range text { offset++ var dbcs bool if utf8.RuneLen(char) > 1 { dbcs = true offset++ } if cnt == int(numCharsArg.Number) { break } if offset+1 > startNum { if dbcs { if cnt+2 > int(numCharsArg.Number) { result += string(char)[:1] break } result += string(char) cnt += 2 } else { result += string(char) cnt++ } } } return newStringFormulaArg(result) } // MID textLen := countUTF16String(text) if startNum > textLen { return newStringFormulaArg("") } startNum-- endNum := startNum + int(numCharsArg.Number) if endNum > textLen+1 { return newStringFormulaArg(string([]rune(text)[startNum:])) } return newStringFormulaArg(string([]rune(text)[startNum:endNum])) } // PROPER converts all characters in a supplied text string to proper case // (i.e. all letters that do not immediately follow another letter are set to // upper case and all other characters are lower case). The syntax of the // function is: // // PROPER(text) func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "PROPER requires 1 argument") } buf := bytes.Buffer{} isLetter := false for _, char := range argsList.Front().Value.(formulaArg).Value() { if !isLetter && unicode.IsLetter(char) { buf.WriteRune(unicode.ToUpper(char)) } else { buf.WriteRune(unicode.ToLower(char)) } isLetter = unicode.IsLetter(char) } return newStringFormulaArg(buf.String()) } // REPLACE function replaces all or part of a text string with another string. // The syntax of the function is: // // REPLACE(old_text,start_num,num_chars,new_text) func (fn *formulaFuncs) REPLACE(argsList *list.List) formulaArg { return fn.replace("REPLACE", argsList) } // REPLACEB replaces part of a text string, based on the number of bytes you // specify, with a different text string. // // REPLACEB(old_text,start_num,num_chars,new_text) func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg { return fn.replace("REPLACEB", argsList) } // replace is an implementation of the formula functions REPLACE and REPLACEB. // TODO: support DBCS include Japanese, Chinese (Simplified), Chinese // (Traditional), and Korean. func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 arguments", name)) } sourceText, targetText := argsList.Front().Value.(formulaArg).Value(), argsList.Back().Value.(formulaArg).Value() startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if startNumArg.Type != ArgNumber { return startNumArg } if numCharsArg.Type != ArgNumber { return numCharsArg } sourceTextLen, startIdx := len(sourceText), int(startNumArg.Number) if startIdx > sourceTextLen { startIdx = sourceTextLen + 1 } endIdx := startIdx + int(numCharsArg.Number) if endIdx > sourceTextLen { endIdx = sourceTextLen + 1 } if startIdx < 1 || endIdx < 1 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } result := sourceText[:startIdx-1] + targetText + sourceText[endIdx-1:] return newStringFormulaArg(result) } // REPT function returns a supplied text string, repeated a specified number // of times. The syntax of the function is: // // REPT(text,number_times) func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments") } text := argsList.Front().Value.(formulaArg) if text.Type != ArgString { return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string") } times := argsList.Back().Value.(formulaArg).ToNumber() if times.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number") } if times.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0") } if times.Number == 0 { return newStringFormulaArg("") } buf := bytes.Buffer{} for i := 0; i < int(times.Number); i++ { buf.WriteString(text.Value()) } return newStringFormulaArg(buf.String()) } // RIGHT function returns a specified number of characters from the end of a // supplied text string. The syntax of the function is: // // RIGHT(text,[num_chars]) func (fn *formulaFuncs) RIGHT(argsList *list.List) formulaArg { return fn.leftRight("RIGHT", argsList) } // RIGHTB returns the last character or characters in a text string, based on // the number of bytes you specify. The syntax of the function is: // // RIGHTB(text,[num_bytes]) func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg { return fn.leftRight("RIGHTB", argsList) } // SEARCH function returns the position of a specified character or sub-string // within a supplied text string. The syntax of the function is: // // SEARCH(search_text,within_text,[start_num]) func (fn *formulaFuncs) SEARCH(argsList *list.List) formulaArg { return fn.find("SEARCH", argsList) } // SEARCHB functions locate one text string within a second text string, and // return the number of the starting position of the first text string from the // first character of the second text string. The syntax of the function is: // // SEARCHB(search_text,within_text,[start_num]) func (fn *formulaFuncs) SEARCHB(argsList *list.List) formulaArg { return fn.find("SEARCHB", argsList) } // SUBSTITUTE function replaces one or more instances of a given text string, // within an original text string. The syntax of the function is: // // SUBSTITUTE(text,old_text,new_text,[instance_num]) func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg { if argsList.Len() != 3 && argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "SUBSTITUTE requires 3 or 4 arguments") } text, sourceText := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg) targetText, instanceNum := argsList.Front().Next().Next().Value.(formulaArg), 0 if argsList.Len() == 3 { return newStringFormulaArg(strings.ReplaceAll(text.Value(), sourceText.Value(), targetText.Value())) } instanceNumArg := argsList.Back().Value.(formulaArg).ToNumber() if instanceNumArg.Type != ArgNumber { return instanceNumArg } instanceNum = int(instanceNumArg.Number) if instanceNum < 1 { return newErrorFormulaArg(formulaErrorVALUE, "instance_num should be > 0") } str, sourceTextLen, count, chars, pos := text.Value(), len(sourceText.Value()), instanceNum, 0, -1 for { count-- index := strings.Index(str, sourceText.Value()) if index == -1 { pos = -1 break } else { pos = index + chars if count == 0 { break } idx := sourceTextLen + index chars += idx str = str[idx:] } } if pos == -1 { return newStringFormulaArg(text.Value()) } pre, post := text.Value()[:pos], text.Value()[pos+sourceTextLen:] return newStringFormulaArg(pre + targetText.Value() + post) } // TEXT function converts a supplied numeric value into text, in a // user-specified format. The syntax of the function is: // // TEXT(value,format_text) func (fn *formulaFuncs) TEXT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "TEXT requires 2 arguments") } value, fmtText := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg) if value.Type == ArgError { return value } if fmtText.Type == ArgError { return fmtText } cellType := CellTypeNumber if num := value.ToNumber(); num.Type != ArgNumber { cellType = CellTypeSharedString } return newStringFormulaArg(format(value.Value(), fmtText.Value(), false, cellType, nil)) } // prepareTextAfterBefore checking and prepare arguments for the formula // functions TEXTAFTER and TEXTBEFORE. func (fn *formulaFuncs) prepareTextAfterBefore(name string, argsList *list.List) formulaArg { argsLen := argsList.Len() if argsLen < 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) } if argsLen > 6 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s accepts at most 6 arguments", name)) } text, delimiter := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg) instanceNum, matchMode, matchEnd, ifNotFound := newNumberFormulaArg(1), newBoolFormulaArg(false), newBoolFormulaArg(false), newEmptyFormulaArg() if argsLen > 2 { instanceNum = argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if instanceNum.Type != ArgNumber { return instanceNum } } if argsLen > 3 { matchMode = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool() if matchMode.Type != ArgNumber { return matchMode } if matchMode.Number == 1 { text, delimiter = newStringFormulaArg(strings.ToLower(text.Value())), newStringFormulaArg(strings.ToLower(delimiter.Value())) } } if argsLen > 4 { matchEnd = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToBool() if matchEnd.Type != ArgNumber { return matchEnd } } if argsLen > 5 { ifNotFound = argsList.Back().Value.(formulaArg) } if text.Value() == "" { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } lenArgsList := list.New().Init() lenArgsList.PushBack(text) textLen := fn.LEN(lenArgsList) if instanceNum.Number == 0 || instanceNum.Number > textLen.Number { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } reverseSearch, startPos := instanceNum.Number < 0, 0.0 if reverseSearch { startPos = textLen.Number } return newListFormulaArg([]formulaArg{ text, delimiter, instanceNum, matchMode, matchEnd, ifNotFound, textLen, newBoolFormulaArg(reverseSearch), newNumberFormulaArg(startPos), }) } // textAfterBeforeSearch is an implementation of the formula functions TEXTAFTER // and TEXTBEFORE. func textAfterBeforeSearch(text string, delimiter []string, startPos int, reverseSearch bool) (int, string) { idx := -1 var modifiedDelimiter string for i := 0; i < len(delimiter); i++ { nextDelimiter := delimiter[i] nextIdx := strings.Index(text[startPos:], nextDelimiter) if nextIdx != -1 { nextIdx += startPos } if reverseSearch { nextIdx = strings.LastIndex(text[:startPos], nextDelimiter) } if idx == -1 || (((nextIdx < idx && !reverseSearch) || (nextIdx > idx && reverseSearch)) && idx != -1) { idx = nextIdx modifiedDelimiter = nextDelimiter } } return idx, modifiedDelimiter } // textAfterBeforeResult is an implementation of the formula functions TEXTAFTER // and TEXTBEFORE. func textAfterBeforeResult(name, modifiedDelimiter string, text []rune, foundIdx, repeatZero, textLen int, matchEndActive, matchEnd, reverseSearch bool) formulaArg { if name == "TEXTAFTER" { endPos := len(modifiedDelimiter) if (repeatZero > 1 || matchEndActive) && matchEnd && reverseSearch { endPos = 0 } if foundIdx+endPos >= textLen { return newEmptyFormulaArg() } return newStringFormulaArg(string(text[foundIdx+endPos : textLen])) } return newStringFormulaArg(string(text[:foundIdx])) } // textAfterBefore is an implementation of the formula functions TEXTAFTER and // TEXTBEFORE. func (fn *formulaFuncs) textAfterBefore(name string, argsList *list.List) formulaArg { args := fn.prepareTextAfterBefore(name, argsList) if args.Type != ArgList { return args } var ( text = []rune(argsList.Front().Value.(formulaArg).Value()) modifiedText = args.List[0].Value() delimiter = []string{args.List[1].Value()} instanceNum = args.List[2].Number matchEnd = args.List[4].Number == 1 ifNotFound = args.List[5] textLen = args.List[6] reverseSearch = args.List[7].Number == 1 foundIdx = -1 repeatZero, startPos int matchEndActive bool modifiedDelimiter string ) if reverseSearch { startPos = int(args.List[8].Number) } for i := 0; i < int(math.Abs(instanceNum)); i++ { foundIdx, modifiedDelimiter = textAfterBeforeSearch(modifiedText, delimiter, startPos, reverseSearch) if foundIdx == 0 { repeatZero++ } if foundIdx == -1 { if matchEnd && i == int(math.Abs(instanceNum))-1 { if foundIdx = int(textLen.Number); reverseSearch { foundIdx = 0 } matchEndActive = true } break } if startPos = foundIdx + len(modifiedDelimiter); reverseSearch { startPos = foundIdx - len(modifiedDelimiter) } } if foundIdx == -1 { return ifNotFound } return textAfterBeforeResult(name, modifiedDelimiter, text, foundIdx, repeatZero, int(textLen.Number), matchEndActive, matchEnd, reverseSearch) } // TEXTAFTER function returns the text that occurs after a given substring or // delimiter. The syntax of the function is: // // TEXTAFTER(text,delimiter,[instance_num],[match_mode],[match_end],[if_not_found]) func (fn *formulaFuncs) TEXTAFTER(argsList *list.List) formulaArg { return fn.textAfterBefore("TEXTAFTER", argsList) } // TEXTBEFORE function returns text that occurs before a given character or // string. The syntax of the function is: // // TEXTBEFORE(text,delimiter,[instance_num],[match_mode],[match_end],[if_not_found]) func (fn *formulaFuncs) TEXTBEFORE(argsList *list.List) formulaArg { return fn.textAfterBefore("TEXTBEFORE", argsList) } // TEXTJOIN function joins together a series of supplied text strings into one // combined text string. The user can specify a delimiter to add between the // individual text items, if required. The syntax of the function is: // // TEXTJOIN([delimiter],[ignore_empty],text1,[text2],...) func (fn *formulaFuncs) TEXTJOIN(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN requires at least 3 arguments") } if argsList.Len() > 252 { return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN accepts at most 252 arguments") } delimiter := argsList.Front().Value.(formulaArg) ignoreEmpty := argsList.Front().Next().Value.(formulaArg) if ignoreEmpty.Type != ArgNumber || !ignoreEmpty.Boolean { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } args, ok := textJoin(argsList.Front().Next().Next(), []string{}, ignoreEmpty.Number != 0) if ok.Type != ArgNumber { return ok } result := strings.Join(args, delimiter.Value()) if countUTF16String(result) > TotalCellChars { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("TEXTJOIN function exceeds %d characters", TotalCellChars)) } return newStringFormulaArg(result) } // textJoin is an implementation of the formula function TEXTJOIN. func textJoin(arg *list.Element, arr []string, ignoreEmpty bool) ([]string, formulaArg) { for arg.Next(); arg != nil; arg = arg.Next() { switch arg.Value.(formulaArg).Type { case ArgError: return arr, arg.Value.(formulaArg) case ArgString, ArgEmpty: val := arg.Value.(formulaArg).Value() if val != "" || !ignoreEmpty { arr = append(arr, val) } case ArgNumber: arr = append(arr, arg.Value.(formulaArg).Value()) case ArgMatrix: for _, row := range arg.Value.(formulaArg).Matrix { argList := list.New().Init() for _, ele := range row { argList.PushBack(ele) } if argList.Len() > 0 { args, _ := textJoin(argList.Front(), []string{}, ignoreEmpty) arr = append(arr, args...) } } } } return arr, newBoolFormulaArg(true) } // TRIM removes extra spaces (i.e. all spaces except for single spaces between // words or characters) from a supplied text string. The syntax of the // function is: // // TRIM(text) func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument") } return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).Value())) } // UNICHAR returns the Unicode character that is referenced by the given // numeric value. The syntax of the function is: // // UNICHAR(number) func (fn *formulaFuncs) UNICHAR(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "UNICHAR requires 1 argument") } numArg := argsList.Front().Value.(formulaArg).ToNumber() if numArg.Type != ArgNumber { return numArg } if numArg.Number <= 0 || numArg.Number > 55295 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newStringFormulaArg(string(rune(numArg.Number))) } // UNICODE function returns the code point for the first character of a // supplied text string. The syntax of the function is: // // UNICODE(text) func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg { return fn.code("UNICODE", argsList) } // UNIQUE function returns a list of unique values in a list or range. The // syntax of the function is: // // UNIQUE(array,[by_col],[exactly_once]) func (fn *formulaFuncs) UNIQUE(argsList *list.List) formulaArg { args, errArg := getFormulaUniqueArgs(argsList) if errArg != nil { return *errArg } if args.byColumn { args.cellRange, args.cols, args.rows = transposeFormulaArgsList(args.cellRange, args.cols, args.rows) } counts := map[string]int{} for i := 0; i < len(args.cellRange); i += args.cols { key := concatValues(args.cellRange[i : i+args.cols]) if _, ok := counts[key]; !ok { counts[key] = 0 } counts[key]++ } uniqueAxes := [][]formulaArg{} for i := 0; i < len(args.cellRange); i += args.cols { key := concatValues(args.cellRange[i : i+args.cols]) if (args.exactlyOnce && counts[key] == 1) || (!args.exactlyOnce && counts[key] >= 1) { uniqueAxes = append(uniqueAxes, args.cellRange[i:i+args.cols]) } delete(counts, key) } if args.byColumn { uniqueAxes = transposeFormulaArgsMatrix(uniqueAxes) } return newMatrixFormulaArg(uniqueAxes) } // transposeFormulaArgsMatrix transposes a 2D slice of formulaArg. func transposeFormulaArgsMatrix(args [][]formulaArg) [][]formulaArg { if len(args) == 0 { return args } transposedArgs := make([][]formulaArg, len(args[0])) for i := 0; i < len(args[0]); i++ { transposedArgs[i] = make([]formulaArg, len(args)) } for i := 0; i < len(args); i++ { for j := 0; j < len(args[i]); j++ { transposedArgs[j][i] = args[i][j] } } return transposedArgs } // transposeFormulaArgsList transposes a flat slice of formulaArg given the // number of columns and rows. func transposeFormulaArgsList(args []formulaArg, cols, rows int) ([]formulaArg, int, int) { transposedArgs := make([]formulaArg, len(args)) for i := 0; i < rows; i++ { for j := 0; j < cols; j++ { transposedArgs[j*rows+i] = args[i*cols+j] } } return transposedArgs, rows, cols } // concatValues concatenates the values of a slice of formulaArg into a single // string. func concatValues(args []formulaArg) string { val := "" for _, arg := range args { // Call to Value is cheap. val += arg.Value() } return val } // uniqueArgs holds the parsed arguments for the UNIQUE function. type uniqueArgs struct { cellRange []formulaArg cols int rows int byColumn bool exactlyOnce bool } // getFormulaUniqueArgs parses and validates the arguments for the UNIQUE // function. func getFormulaUniqueArgs(argsList *list.List) (uniqueArgs, *formulaArg) { res := uniqueArgs{} argsLen := argsList.Len() if argsLen == 0 { errArg := newErrorFormulaArg(formulaErrorVALUE, "UNIQUE requires at least 1 argument") return res, &errArg } if argsLen > 3 { msg := fmt.Sprintf("UNIQUE takes at most 3 arguments, received %d arguments", argsLen) errArg := newErrorFormulaArg(formulaErrorVALUE, msg) return res, &errArg } firstArg := argsList.Front() res.cellRange = firstArg.Value.(formulaArg).ToList() if len(res.cellRange) == 0 { errArg := newErrorFormulaArg(formulaErrorVALUE, "missing first argument to UNIQUE") return res, &errArg } if res.cellRange[0].Type == ArgError { return res, &res.cellRange[0] } rmin, rmax := calcColsRowsMinMax(false, argsList) cmin, cmax := calcColsRowsMinMax(true, argsList) res.cols, res.rows = cmax-cmin+1, rmax-rmin+1 secondArg := firstArg.Next() if secondArg == nil { return res, nil } argByColumn := secondArg.Value.(formulaArg).ToBool() if argByColumn.Type == ArgError { return res, &argByColumn } res.byColumn = (argByColumn.Value() == "TRUE") thirdArg := secondArg.Next() if thirdArg == nil { return res, nil } argExactlyOnce := thirdArg.Value.(formulaArg).ToBool() if argExactlyOnce.Type == ArgError { return res, &argExactlyOnce } res.exactlyOnce = (argExactlyOnce.Value() == "TRUE") return res, nil } // UPPER converts all characters in a supplied text string to upper case. The // syntax of the function is: // // UPPER(text) func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument") } return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).Value())) } // VALUE function converts a text string into a numeric value. The syntax of // the function is: // // VALUE(text) func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "VALUE requires 1 argument") } text := strings.ReplaceAll(argsList.Front().Value.(formulaArg).Value(), ",", "") percent := 1.0 if strings.HasSuffix(text, "%") { percent, text = 0.01, strings.TrimSuffix(text, "%") } decimal := big.Float{} if _, ok := decimal.SetString(text); ok { value, _ := decimal.Float64() return newNumberFormulaArg(value * percent) } dateValue, timeValue, errTime := 0.0, 0.0, false if !isDateOnlyFmt(text) { h, m, s, _, _, err := strToTime(text) errTime = err.Type == ArgError if !errTime { timeValue = (float64(h)*3600 + float64(m)*60 + s) / 86400 } } y, m, d, _, err := strToDate(text) errDate := err.Type == ArgError if !errDate { dateValue = daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1 } if errTime && errDate { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newNumberFormulaArg(dateValue + timeValue) } // VALUETOTEXT function returns text from any specified value. It passes text // values unchanged, and converts non-text values to text. // // VALUETOTEXT(value,[format]) func (fn *formulaFuncs) VALUETOTEXT(argsList *list.List) formulaArg { format := prepareToText("VALUETOTEXT", argsList) if format.Type != ArgNumber { return format } cell := argsList.Front().Value.(formulaArg) if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 { return newStringFormulaArg(fmt.Sprintf("\"%s\"", cell.Value())) } return newStringFormulaArg(cell.Value()) } // Conditional Functions // IF function tests a supplied condition and returns one result if the // condition evaluates to TRUE, and another result if the condition evaluates // to FALSE. The syntax of the function is: // // IF(logical_test,value_if_true,value_if_false) func (fn *formulaFuncs) IF(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "IF requires at least 1 argument") } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "IF accepts at most 3 arguments") } token := fn.implicitIntersect(argsList.Front().Value.(formulaArg)) var ( cond bool err error result formulaArg ) switch token.Type { case ArgString: if cond, err = strconv.ParseBool(token.Value()); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } case ArgNumber: cond = token.Number == 1 } if argsList.Len() == 1 { return newBoolFormulaArg(cond) } if cond { value := fn.implicitIntersect(argsList.Front().Next().Value.(formulaArg)) switch value.Type { case ArgNumber: result = value.ToNumber() default: result = newStringFormulaArg(value.Value()) } return result } if argsList.Len() == 3 { value := fn.implicitIntersect(argsList.Back().Value.(formulaArg)) switch value.Type { case ArgNumber: result = value.ToNumber() default: result = newStringFormulaArg(value.Value()) } } return result } // Lookup and Reference Functions // ADDRESS function takes a row and a column number and returns a cell // reference as a text string. The syntax of the function is: // // ADDRESS(row_num,column_num,[abs_num],[a1],[sheet_text]) func (fn *formulaFuncs) ADDRESS(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at least 2 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at most 5 arguments") } rowNum := argsList.Front().Value.(formulaArg).ToNumber() if rowNum.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if rowNum.Number > TotalRows { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } colNum := argsList.Front().Next().Value.(formulaArg).ToNumber() if colNum.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } absNum := newNumberFormulaArg(1) if argsList.Len() >= 3 { absNum = argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if absNum.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } if absNum.Number < 1 || absNum.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } a1 := newBoolFormulaArg(true) if argsList.Len() >= 4 { a1 = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool() if a1.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } var sheetText string if argsList.Len() == 5 { sheetText = fmt.Sprintf("%s!", argsList.Back().Value.(formulaArg).Value()) } formatter := addressFmtMaps[fmt.Sprintf("%d_%s", int(absNum.Number), a1.Value())] addr, err := formatter(int(colNum.Number), int(rowNum.Number)) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newStringFormulaArg(fmt.Sprintf("%s%s", sheetText, addr)) } // ANCHORARRAY function returns the entire spilled range for the dynamic array // in cell. The syntax of the function is: // // ANCHORARRAY(cell) func (fn *formulaFuncs) ANCHORARRAY(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ANCHORARRAY requires 1 numeric argument") } ws, err := fn.f.workSheetReader(fn.sheet) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } ref := argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef) cell := ws.SheetData.Row[ref.Row-1].C[ref.Col-1] if cell.F == nil { return newEmptyFormulaArg() } coordinates, err := rangeRefToCoordinates(cell.F.Ref) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } _ = sortCoordinates(coordinates) var mtx [][]formulaArg for c := coordinates[0]; c <= coordinates[2]; c++ { var row []formulaArg for r := coordinates[1]; r <= coordinates[3]; r++ { cellName, _ := CoordinatesToCellName(c, r) result, err := fn.f.CalcCellValue(ref.Sheet, cellName, Options{RawCellValue: true}) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } arg := newStringFormulaArg(result) if num := arg.ToNumber(); num.Type == ArgNumber { arg = num } row = append(row, arg) } mtx = append(mtx, row) } return newMatrixFormulaArg(mtx) } // CHOOSE function returns a value from an array, that corresponds to a // supplied index number (position). The syntax of the function is: // // CHOOSE(index_num,value1,[value2],...) func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments") } idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).Value()) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number") } if argsList.Len() <= idx { return newErrorFormulaArg(formulaErrorVALUE, "index_num should be <= to the number of values") } arg := argsList.Front() for i := 0; i < idx; i++ { arg = arg.Next() } return arg.Value.(formulaArg) } // matchPatternToRegExp convert find text pattern to regular expression. func matchPatternToRegExp(findText string, dbcs bool) (string, bool) { var ( exp string wildCard bool mark = "." ) if dbcs { mark = "(?:(?:[\\x00-\\x0081])|(?:[\\xFF61-\\xFFA0])|(?:[\\xF8F1-\\xF8F4])|[0-9A-Za-z])" } for _, char := range findText { if strings.ContainsAny(string(char), ".+$^[](){}|/") { exp += fmt.Sprintf("\\%s", string(char)) continue } if char == '?' { wildCard = true exp += mark continue } if char == '*' { wildCard = true exp += ".*" continue } exp += string(char) } return fmt.Sprintf("^%s", exp), wildCard } // matchPattern finds whether the text matches or satisfies the pattern // string. The pattern supports '*' and '?' wildcards in the pattern string. func matchPattern(findText, withinText string, dbcs bool, startNum int) (int, bool) { exp, wildCard := matchPatternToRegExp(findText, dbcs) offset := 1 for idx := range withinText { if offset < startNum { offset++ continue } if wildCard { if ok, _ := regexp.MatchString(exp, withinText[idx:]); ok { break } } if strings.Index(withinText[idx:], findText) == 0 { break } offset++ } return offset, countUTF16String(withinText) != offset-1 } // compareFormulaArg compares the left-hand sides and the right-hand sides' // formula arguments by given conditions such as case-sensitive, if exact // match, and make compare result as formula criteria condition type. func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { if lhs.Type != rhs.Type { return criteriaNe } switch lhs.Type { case ArgNumber: if lhs.Number == rhs.Number { return criteriaEq } if lhs.Number < rhs.Number { return criteriaL } return criteriaG case ArgString: ls, rs := lhs.Value(), rhs.Value() if !caseSensitive { ls, rs = strings.ToLower(ls), strings.ToLower(rs) } if matchMode.Number == matchModeWildcard { if _, ok := matchPattern(rs, ls, false, 0); ok { return criteriaEq } } return map[int]byte{1: criteriaG, -1: criteriaL, 0: criteriaEq}[strings.Compare(ls, rs)] case ArgEmpty: return criteriaEq case ArgList: return compareFormulaArgList(lhs, rhs, matchMode, caseSensitive) case ArgMatrix: return compareFormulaArgMatrix(lhs, rhs, matchMode, caseSensitive) default: return criteriaErr } } // compareFormulaArgList compares the left-hand sides and the right-hand sides // list type formula arguments. func compareFormulaArgList(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { if len(lhs.List) < len(rhs.List) { return criteriaL } if len(lhs.List) > len(rhs.List) { return criteriaG } for arg := range lhs.List { criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], matchMode, caseSensitive) if criteria != criteriaEq { return criteria } } return criteriaEq } // compareFormulaArgMatrix compares the left-hand sides and the right-hand sides' // matrix type formula arguments. func compareFormulaArgMatrix(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { if len(lhs.Matrix) < len(rhs.Matrix) { return criteriaL } if len(lhs.Matrix) > len(rhs.Matrix) { return criteriaG } for i := range lhs.Matrix { left, right := lhs.Matrix[i], rhs.Matrix[i] if len(left) < len(right) { return criteriaL } if len(left) > len(right) { return criteriaG } for arg := range left { criteria := compareFormulaArg(left[arg], right[arg], matchMode, caseSensitive) if criteria != criteriaEq { return criteria } } } return criteriaEq } // COLUMN function returns the first column number within a supplied reference // or the number of the current column. The syntax of the function is: // // COLUMN([reference]) func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg { if argsList.Len() > 1 { return newErrorFormulaArg(formulaErrorVALUE, "COLUMN requires at most 1 argument") } if argsList.Len() == 1 { if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Col)) } if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Col)) } return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") } col, _, _ := CellNameToCoordinates(fn.cell) return newNumberFormulaArg(float64(col)) } // calcColsRowsMinMax calculation min and max value for given formula arguments // sequence of the formula functions COLUMNS and ROWS. func calcColsRowsMinMax(cols bool, argsList *list.List) (minVal, maxVal int) { getVal := func(cols bool, cell cellRef) int { if cols { return cell.Col } return cell.Row } if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { crs := argsList.Front().Value.(formulaArg).cellRanges for cr := crs.Front(); cr != nil; cr = cr.Next() { if minVal == 0 { minVal = getVal(cols, cr.Value.(cellRange).From) } if maxVal < getVal(cols, cr.Value.(cellRange).To) { maxVal = getVal(cols, cr.Value.(cellRange).To) } } } if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { cr := argsList.Front().Value.(formulaArg).cellRefs for refs := cr.Front(); refs != nil; refs = refs.Next() { if minVal == 0 { minVal = getVal(cols, refs.Value.(cellRef)) } if maxVal < getVal(cols, refs.Value.(cellRef)) { maxVal = getVal(cols, refs.Value.(cellRef)) } } } return } // COLUMNS function receives an Excel range and returns the number of columns // that are contained within the range. The syntax of the function is: // // COLUMNS(array) func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument") } minVal, maxVal := calcColsRowsMinMax(true, argsList) if maxVal == MaxColumns { return newNumberFormulaArg(float64(MaxColumns)) } result := maxVal - minVal + 1 if maxVal == minVal { if minVal == 0 { return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") } return newNumberFormulaArg(float64(1)) } return newNumberFormulaArg(float64(result)) } // FORMULATEXT function returns a formula as a text string. The syntax of the // function is: // // FORMULATEXT(reference) func (fn *formulaFuncs) FORMULATEXT(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "FORMULATEXT requires 1 argument") } refs := argsList.Front().Value.(formulaArg).cellRefs col, row := 0, 0 if refs != nil && refs.Len() > 0 { col, row = refs.Front().Value.(cellRef).Col, refs.Front().Value.(cellRef).Row } ranges := argsList.Front().Value.(formulaArg).cellRanges if ranges != nil && ranges.Len() > 0 { col, row = ranges.Front().Value.(cellRange).From.Col, ranges.Front().Value.(cellRange).From.Row } cell, err := CoordinatesToCellName(col, row) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } formula, _ := fn.f.GetCellFormula(fn.sheet, cell) return newStringFormulaArg(formula) } // checkHVLookupArgs checking arguments, prepare extract mode, lookup value, // and data for the formula functions HLOOKUP and VLOOKUP. func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue, tableArray, matchMode, errArg formulaArg) { unit := map[string]string{ "HLOOKUP": "row", "VLOOKUP": "col", }[name] if argsList.Len() < 3 { errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name)) return } if argsList.Len() > 4 { errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 4 arguments", name)) return } lookupValue = argsList.Front().Value.(formulaArg) tableArray = argsList.Front().Next().Value.(formulaArg) if tableArray.Type != ArgMatrix { errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires second argument of table array", name)) return } arg := argsList.Front().Next().Next().Value.(formulaArg) if arg.Type != ArgNumber || arg.Boolean { errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit)) return } idx, matchMode = int(arg.Number)-1, newNumberFormulaArg(matchModeMaxLess) if argsList.Len() == 4 { rangeLookup := argsList.Back().Value.(formulaArg).ToBool() if rangeLookup.Type == ArgError { errArg = rangeLookup return } if rangeLookup.Number == 0 { matchMode = newNumberFormulaArg(matchModeWildcard) } } return } // HLOOKUP function 'looks up' a given value in the top row of a data array // (or table), and returns the corresponding value from another row of the // array. The syntax of the function is: // // HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup]) func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg { rowIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("HLOOKUP", argsList) if errArg.Type == ArgError { return errArg } var matchIdx int var wasExact bool if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows { matchIdx, wasExact = lookupLinearSearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear)) } else { matchIdx, wasExact = lookupBinarySearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary)) } if matchIdx == -1 { return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found") } if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) { return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index") } row := tableArray.Matrix[rowIdx] if wasExact || matchMode.Number == matchModeWildcard { return row[matchIdx] } return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found") } // HYPERLINK function creates a hyperlink to a specified location. The syntax // of the function is: // // HYPERLINK(link_location,[friendly_name]) func (fn *formulaFuncs) HYPERLINK(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "HYPERLINK requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "HYPERLINK allows at most 2 arguments") } return newStringFormulaArg(argsList.Back().Value.(formulaArg).Value()) } // calcMatch returns the position of the value by given match type, criteria // and lookup array for the formula function MATCH. // matchType only contains -1, and 1. func calcMatchMatrix(vertical bool, matchType int, criteria *formulaCriteria, lookupArray [][]formulaArg) formulaArg { idx := -1 calc := func(i int, arg formulaArg) bool { switch matchType { case -1: if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{ Type: criteriaGe, Condition: criteria.Condition, }); ok { idx = i return false } if criteria.Condition.Type == ArgNumber { return true } case 1: if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{ Type: criteriaLe, Condition: criteria.Condition, }); ok { idx = i return false } if criteria.Condition.Type == ArgNumber { return true } } return false } if vertical { for i, row := range lookupArray { if ok := calc(i, row[0]); ok { break } } } else { for i, cell := range lookupArray[0] { if ok := calc(i, cell); ok { break } } } if idx == -1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(float64(idx + 1)) } // calcMatch returns the position of the value by given match type, criteria // and lookup array for the formula function MATCH. func calcMatch(matchType int, criteria *formulaCriteria, lookupArray []formulaArg) formulaArg { idx := -1 switch matchType { case 0: for i, arg := range lookupArray { if ok, _ := formulaCriteriaEval(arg, criteria); ok { return newNumberFormulaArg(float64(i + 1)) } } case -1: for i, arg := range lookupArray { if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{ Type: criteriaGe, Condition: criteria.Condition, }); ok { idx = i continue } if criteria.Condition.Type == ArgNumber { break } } case 1: for i, arg := range lookupArray { if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{ Type: criteriaLe, Condition: criteria.Condition, }); ok { idx = i continue } if criteria.Condition.Type == ArgNumber { break } } } if idx == -1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } return newNumberFormulaArg(float64(idx + 1)) } // MATCH function looks up a value in an array, and returns the position of // the value within the array. The user can specify that the function should // only return a result if an exact match is found, or that the function // should return the position of the closest match (above or below), if an // exact match is not found. The syntax of the Match function is: // // MATCH(lookup_value,lookup_array,[match_type]) func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg { if argsList.Len() != 2 && argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires 1 or 2 arguments") } var ( matchType = 1 lookupArray []formulaArg lookupArrayArg = argsList.Front().Next().Value.(formulaArg) lookupArrayErr = "MATCH arguments lookup_array should be one-dimensional array" ) if argsList.Len() == 3 { matchTypeArg := argsList.Back().Value.(formulaArg).ToNumber() if matchTypeArg.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires numeric match_type argument") } if matchTypeArg.Number == -1 || matchTypeArg.Number == 0 { matchType = int(matchTypeArg.Number) } } switch lookupArrayArg.Type { case ArgMatrix: if len(lookupArrayArg.Matrix) != 1 && len(lookupArrayArg.Matrix[0]) != 1 { return newErrorFormulaArg(formulaErrorNA, lookupArrayErr) } lookupArray = lookupArrayArg.ToList() default: return newErrorFormulaArg(formulaErrorNA, lookupArrayErr) } return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg)), lookupArray) } // TRANSPOSE function 'transposes' an array of cells (i.e. the function copies // a horizontal range of cells into a vertical range and vice versa). The // syntax of the function is: // // TRANSPOSE(array) func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument") } args := argsList.Back().Value.(formulaArg).ToList() rmin, rmax := calcColsRowsMinMax(false, argsList) cmin, cmax := calcColsRowsMinMax(true, argsList) cols, rows := cmax-cmin+1, rmax-rmin+1 src := make([][]formulaArg, 0) for i := 0; i < len(args); i += cols { src = append(src, args[i:i+cols]) } mtx := make([][]formulaArg, cols) for r, row := range src { colIdx := r % rows for c, cell := range row { rowIdx := c % cols if len(mtx[rowIdx]) == 0 { mtx[rowIdx] = make([]formulaArg, rows) } mtx[rowIdx][colIdx] = cell } } return newMatrixFormulaArg(mtx) } // lookupLinearSearch sequentially checks each look value of the lookup array until // a match is found or the whole list has been searched. func lookupLinearSearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (int, bool) { matchIdx, wasExact := -1, false linearSearch := func(i int, cell, lhs formulaArg) bool { if lookupValue.Type == ArgNumber { if lhs = cell.ToNumber(); lhs.Type == ArgError { lhs = cell } } else if lookupValue.Type == ArgMatrix { lhs = lookupArray } else if lookupArray.Type == ArgString { lhs = newStringFormulaArg(cell.Value()) } if compareFormulaArg(lhs, lookupValue, matchMode, false) == criteriaEq { matchIdx = i wasExact = true if searchMode.Number == searchModeLinear { return true } } if matchMode.Number == matchModeMinGreater || matchMode.Number == matchModeMaxLess { matchIdx = int(calcMatchMatrix(vertical, int(matchMode.Number), formulaCriteriaParser(lookupValue), lookupArray.Matrix).Number) return false } return false } if vertical { for i, row := range lookupArray.Matrix { lhs := row[0] if linearSearch(i, lhs, lhs) { break } } } else { for i, lhs := range lookupArray.Matrix[0] { if linearSearch(i, lhs, lhs) { break } } } return matchIdx, wasExact } // VLOOKUP function 'looks up' a given value in the left-hand column of a // data array (or table), and returns the corresponding value from another // column of the array. The syntax of the function is: // // VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup]) func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg { colIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("VLOOKUP", argsList) if errArg.Type == ArgError { return errArg } var matchIdx int var wasExact bool if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows { matchIdx, wasExact = lookupLinearSearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear)) } else { matchIdx, wasExact = lookupBinarySearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary)) } if matchIdx == -1 { return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") } mtx := tableArray.Matrix[matchIdx] if colIdx < 0 || colIdx >= len(mtx) { return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index") } if wasExact || matchMode.Number == matchModeWildcard { return mtx[colIdx] } return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") } // lookupBinarySearch finds the position of a target value when range lookup // is TRUE, if the data of table array can't guarantee be sorted, it will // return wrong result. func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (matchIdx int, wasExact bool) { var tableArray []formulaArg if vertical { for _, row := range lookupArray.Matrix { tableArray = append(tableArray, row[0]) } } else { tableArray = lookupArray.Matrix[0] } low, high, lastMatchIdx := 0, len(tableArray)-1, -1 count := high for low <= high { mid := low + (high-low)/2 cell := tableArray[mid] lhs := cell if lookupValue.Type == ArgNumber { if lhs = cell.ToNumber(); lhs.Type == ArgError { lhs = cell } } else if lookupValue.Type == ArgMatrix && vertical { lhs = lookupArray } else if lookupValue.Type == ArgString { lhs = newStringFormulaArg(cell.Value()) } result := compareFormulaArg(lhs, lookupValue, matchMode, false) switch result { case criteriaEq: matchIdx, wasExact = mid, true if searchMode.Number == searchModeDescBinary { matchIdx = count - matchIdx } return case criteriaG: high = mid - 1 case criteriaL: matchIdx = mid if cell.Type != ArgEmpty { lastMatchIdx = matchIdx } low = mid + 1 default: return -1, false } } matchIdx, wasExact = lastMatchIdx, true return } // checkLookupArgs checking arguments, prepare lookup value, and data for the // formula function LOOKUP. func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVector, errArg formulaArg) { if argsList.Len() < 2 { errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments") return } if argsList.Len() > 3 { errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments") return } lookupValue = newStringFormulaArg(argsList.Front().Value.(formulaArg).Value()) lookupVector = argsList.Front().Next().Value.(formulaArg) if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList { errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array") return } arrayForm = lookupVector.Type == ArgMatrix if arrayForm && len(lookupVector.Matrix) == 0 { errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires not empty range as second argument") } return } // iterateLookupArgs iterate arguments to extract columns and calculate match // index for the formula function LOOKUP. func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int, bool) { cols, matchIdx, ok := lookupCol(lookupVector, 0), -1, false for idx, col := range cols { lhs := lookupValue switch col.Type { case ArgNumber: lhs = lhs.ToNumber() if !col.Boolean { if lhs.Type == ArgError { lhs = lookupValue } } } compare := compareFormulaArg(lhs, col, newNumberFormulaArg(matchModeMaxLess), false) // Find exact match if compare == criteriaEq { matchIdx = idx break } // Find the nearest match if lookup value is more than or equal to the first value in lookup vector if idx == 0 { ok = compare == criteriaG } else if ok && compare == criteriaL && matchIdx == -1 { matchIdx = idx - 1 } } return cols, matchIdx, ok } // index is an implementation of the formula function INDEX. func (fn *formulaFuncs) index(array formulaArg, rowIdx, colIdx int) formulaArg { var cells []formulaArg if array.Type == ArgMatrix { cellMatrix := array.Matrix if rowIdx < -1 || rowIdx >= len(cellMatrix) { return newErrorFormulaArg(formulaErrorREF, "INDEX row_num out of range") } if rowIdx == -1 { if colIdx >= len(cellMatrix[0]) { return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range") } var column [][]formulaArg for _, cells = range cellMatrix { column = append(column, []formulaArg{cells[colIdx]}) } return newMatrixFormulaArg(column) } cells = cellMatrix[rowIdx] } if colIdx < -1 || colIdx >= len(cells) { return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range") } return newListFormulaArg(cells) } // validateMatchMode check the number of match mode if be equal to 0, 1, -1 or // 2. func validateMatchMode(mode float64) bool { return mode == matchModeExact || mode == matchModeMinGreater || mode == matchModeMaxLess || mode == matchModeWildcard } // validateSearchMode check the number of search mode if be equal to 1, -1, 2 // or -2. func validateSearchMode(mode float64) bool { return mode == searchModeLinear || mode == searchModeReverseLinear || mode == searchModeAscBinary || mode == searchModeDescBinary } // prepareXlookupArgs checking and prepare arguments for the formula function // XLOOKUP. func (fn *formulaFuncs) prepareXlookupArgs(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP requires at least 3 arguments") } if argsList.Len() > 6 { return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP allows at most 6 arguments") } lookupValue := argsList.Front().Value.(formulaArg) lookupArray := argsList.Front().Next().Value.(formulaArg) returnArray := argsList.Front().Next().Next().Value.(formulaArg) ifNotFond := newErrorFormulaArg(formulaErrorNA, formulaErrorNA) matchMode, searchMode := newNumberFormulaArg(matchModeExact), newNumberFormulaArg(searchModeLinear) if argsList.Len() > 3 { ifNotFond = argsList.Front().Next().Next().Next().Value.(formulaArg) } if argsList.Len() > 4 { if matchMode = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); matchMode.Type != ArgNumber { return matchMode } } if argsList.Len() > 5 { if searchMode = argsList.Back().Value.(formulaArg).ToNumber(); searchMode.Type != ArgNumber { return searchMode } } if lookupArray.Type != ArgMatrix || returnArray.Type != ArgMatrix { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if !validateMatchMode(matchMode.Number) || !validateSearchMode(searchMode.Number) { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return newListFormulaArg([]formulaArg{lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode}) } // xlookup is an implementation of the formula function XLOOKUP. func (fn *formulaFuncs) xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx int, condition1, condition2, condition3, condition4 bool, returnArray formulaArg, ) formulaArg { var result [][]formulaArg for rowIdx, row := range returnArray.Matrix { for colIdx, cell := range row { if condition1 { if condition2 { result = append(result, []formulaArg{cell}) continue } if returnArrayRows > 1 && returnArrayCols > 1 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } if condition3 { if returnArrayCols != lookupCols { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if colIdx == matchIdx { result = append(result, []formulaArg{cell}) continue } } if condition4 { if returnArrayRows != lookupRows { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if rowIdx == matchIdx { if len(result) == 0 { result = append(result, []formulaArg{cell}) continue } result[0] = append(result[0], cell) } } } } array := newMatrixFormulaArg(result) cells := array.ToList() if len(cells) == 1 { return cells[0] } return array } // XLOOKUP function searches a range or an array, and then returns the item // corresponding to the first match it finds. If no match exists, then // XLOOKUP can return the closest (approximate) match. The syntax of the // function is: // // XLOOKUP(lookup_value,lookup_array,return_array,[if_not_found],[match_mode],[search_mode]) func (fn *formulaFuncs) XLOOKUP(argsList *list.List) formulaArg { args := fn.prepareXlookupArgs(argsList) if args.Type != ArgList { return args } lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] lookupRows, lookupCols := len(lookupArray.Matrix), 0 if lookupRows > 0 { lookupCols = len(lookupArray.Matrix[0]) } if lookupRows != 1 && lookupCols != 1 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } verticalLookup := lookupRows >= lookupCols var matchIdx int switch searchMode.Number { case searchModeLinear, searchModeReverseLinear: matchIdx, _ = lookupLinearSearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode) default: matchIdx, _ = lookupBinarySearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode) } if matchIdx == -1 { return ifNotFond } returnArrayRows, returnArrayCols := len(returnArray.Matrix), len(returnArray.Matrix[0]) condition1 := lookupRows == 1 && lookupCols == 1 condition2 := returnArrayRows == 1 || returnArrayCols == 1 condition3 := lookupRows == 1 && lookupCols > 1 condition4 := lookupRows > 1 && lookupCols == 1 return fn.xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx, condition1, condition2, condition3, condition4, returnArray) } // INDEX function returns a reference to a cell that lies in a specified row // and column of a range of cells. The syntax of the function is: // // INDEX(array,row_num,[col_num]) func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg { if argsList.Len() < 2 || argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, "INDEX requires 2 or 3 arguments") } array := argsList.Front().Value.(formulaArg) if array.Type != ArgMatrix && array.Type != ArgList { array = newMatrixFormulaArg([][]formulaArg{{array}}) } rowArg := argsList.Front().Next().Value.(formulaArg).ToNumber() if rowArg.Type != ArgNumber { return rowArg } rowIdx, colIdx := int(rowArg.Number)-1, -1 if argsList.Len() == 3 { colArg := argsList.Back().Value.(formulaArg).ToNumber() if colArg.Type != ArgNumber { return colArg } colIdx = int(colArg.Number) - 1 } if rowIdx == -1 && colIdx == -1 { if len(array.ToList()) != 1 { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return array.ToList()[0] } cells := fn.index(array, rowIdx, colIdx) if cells.Type != ArgList { return cells } if colIdx == -1 { return newMatrixFormulaArg([][]formulaArg{cells.List}) } return cells.List[colIdx] } // INDIRECT function converts a text string into a cell reference. The syntax // of the Indirect function is: // // INDIRECT(ref_text,[a1]) func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg { if argsList.Len() != 1 && argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "INDIRECT requires 1 or 2 arguments") } refText := argsList.Front().Value.(formulaArg).Value() a1 := newBoolFormulaArg(true) if argsList.Len() == 2 { if a1 = argsList.Back().Value.(formulaArg).ToBool(); a1.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } R1C1ToA1 := func(ref string) (cell string, err error) { parts := strings.Split(strings.TrimLeft(ref, "R"), "C") if len(parts) != 2 { return } row, err := strconv.Atoi(parts[0]) if err != nil { return } col, err := strconv.Atoi(parts[1]) if err != nil { return } cell, err = CoordinatesToCellName(col, row) return } refs := strings.Split(refText, ":") fromRef, toRef := refs[0], "" if len(refs) == 2 { toRef = refs[1] } if a1.Number == 0 { from, err := R1C1ToA1(refs[0]) if err != nil { return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } fromRef = from if len(refs) == 2 { to, err := R1C1ToA1(refs[1]) if err != nil { return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } toRef = to } } if len(refs) == 1 { value, err := fn.f.GetCellValue(fn.sheet, fromRef) if err != nil { return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) } return newStringFormulaArg(value) } arg, _ := fn.f.parseReference(fn.ctx, fn.sheet, fromRef+":"+toRef) return arg } // LOOKUP function performs an approximate match lookup in a one-column or // one-row range, and returns the corresponding value from another one-column // or one-row range. The syntax of the function is: // // LOOKUP(lookup_value,lookup_vector,[result_vector]) func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg { arrayForm, lookupValue, lookupVector, errArg := checkLookupArgs(argsList) if errArg.Type == ArgError { return errArg } cols, matchIdx, ok := iterateLookupArgs(lookupValue, lookupVector) if ok && matchIdx == -1 { matchIdx = len(cols) - 1 } var column []formulaArg if argsList.Len() == 3 { column = lookupCol(argsList.Back().Value.(formulaArg), 0) } else if arrayForm && len(lookupVector.Matrix[0]) > 1 { column = lookupCol(lookupVector, 1) } else { column = cols } if matchIdx < 0 || matchIdx >= len(column) { return newErrorFormulaArg(formulaErrorNA, "LOOKUP no result found") } return column[matchIdx] } // lookupCol extract columns for LOOKUP. func lookupCol(arr formulaArg, idx int) []formulaArg { col := arr.List if arr.Type == ArgMatrix { col = nil for _, r := range arr.Matrix { if len(r) > 0 { col = append(col, r[idx]) continue } col = append(col, newEmptyFormulaArg()) } } return col } // ROW function returns the first row number within a supplied reference or // the number of the current row. The syntax of the function is: // // ROW([reference]) func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg { if argsList.Len() > 1 { return newErrorFormulaArg(formulaErrorVALUE, "ROW requires at most 1 argument") } if argsList.Len() == 1 { if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Row)) } if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Row)) } return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") } _, row, _ := CellNameToCoordinates(fn.cell) return newNumberFormulaArg(float64(row)) } // ROWS function takes an Excel range and returns the number of rows that are // contained within the range. The syntax of the function is: // // ROWS(array) func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument") } minVal, maxVal := calcColsRowsMinMax(false, argsList) if maxVal == TotalRows { return newNumberFormulaArg(TotalRows) } result := maxVal - minVal + 1 if maxVal == minVal { if minVal == 0 { return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") } return newNumberFormulaArg(float64(1)) } return newNumberFormulaArg(float64(result)) } // Web Functions // ENCODEURL function returns a URL-encoded string, replacing certain // non-alphanumeric characters with the percentage symbol (%) and a // hexadecimal number. The syntax of the function is: // // ENCODEURL(url) func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument") } token := argsList.Front().Value.(formulaArg).Value() return newStringFormulaArg(strings.ReplaceAll(url.QueryEscape(token), "+", "%20")) } // Financial Functions // validateFrequency check the number of coupon payments per year if be equal to 1, 2 or 4. func validateFrequency(freq float64) bool { return freq == 1 || freq == 2 || freq == 4 } // ACCRINT function returns the accrued interest in a security that pays // periodic interest. The syntax of the function is: // // ACCRINT(issue,first_interest,settlement,rate,par,frequency,[basis],[calc_method]) func (fn *formulaFuncs) ACCRINT(argsList *list.List) formulaArg { if argsList.Len() < 6 { return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT requires at least 6 arguments") } if argsList.Len() > 8 { return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT allows at most 8 arguments") } args := fn.prepareDataValueArgs(3, argsList) if args.Type != ArgList { return args } issue, settlement := args.List[0], args.List[2] rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() par := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber || par.Type != ArgNumber || frequency.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() >= 7 { if basis = argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } if argsList.Len() == 8 { if cm := argsList.Back().Value.(formulaArg).ToBool(); cm.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } frac1 := yearFrac(issue.Number, settlement.Number, int(basis.Number)) if frac1.Type != ArgNumber { return frac1 } return newNumberFormulaArg(par.Number * rate.Number * frac1.Number) } // ACCRINTM function returns the accrued interest in a security that pays // interest at maturity. The syntax of the function is: // // ACCRINTM(issue,settlement,rate,[par],[basis]) func (fn *formulaFuncs) ACCRINTM(argsList *list.List) formulaArg { if argsList.Len() != 4 && argsList.Len() != 5 { return newErrorFormulaArg(formulaErrorVALUE, "ACCRINTM requires 4 or 5 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } issue, settlement := args.List[0], args.List[1] if settlement.Number < issue.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() par := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber || par.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if par.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 5 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } frac := yearFrac(issue.Number, settlement.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } return newNumberFormulaArg(frac.Number * rate.Number * par.Number) } // prepareAmorArgs checking and prepare arguments for the formula functions // AMORDEGRC and AMORLINC. func (fn *formulaFuncs) prepareAmorArgs(name string, argsList *list.List) formulaArg { cost := argsList.Front().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost to be number argument", name)) } if cost.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost >= 0", name)) } args := list.New().Init() args.PushBack(argsList.Front().Next().Value.(formulaArg)) datePurchased := fn.DATEVALUE(args) if datePurchased.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } args.Init() args.PushBack(argsList.Front().Next().Next().Value.(formulaArg)) firstPeriod := fn.DATEVALUE(args) if firstPeriod.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if firstPeriod.Number < datePurchased.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } salvage := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if salvage.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if salvage.Number < 0 || salvage.Number > cost.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } period := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if period.Type != ArgNumber || period.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } rate := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber || rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 7 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newListFormulaArg([]formulaArg{cost, datePurchased, firstPeriod, salvage, period, rate, basis}) } // AMORDEGRC function is provided for users of the French accounting system. // The function calculates the prorated linear depreciation of an asset for a // specified accounting period. The syntax of the function is: // // AMORDEGRC(cost,date_purchased,first_period,salvage,period,rate,[basis]) func (fn *formulaFuncs) AMORDEGRC(argsList *list.List) formulaArg { if argsList.Len() != 6 && argsList.Len() != 7 { return newErrorFormulaArg(formulaErrorVALUE, "AMORDEGRC requires 6 or 7 arguments") } args := fn.prepareAmorArgs("AMORDEGRC", argsList) if args.Type != ArgList { return args } cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6] if rate.Number >= 0.5 { return newErrorFormulaArg(formulaErrorNUM, "AMORDEGRC requires rate to be < 0.5") } assetsLife, amorCoeff := 1/rate.Number, 2.5 if assetsLife < 3 { amorCoeff = 1 } else if assetsLife < 5 { amorCoeff = 1.5 } else if assetsLife <= 6 { amorCoeff = 2 } rate.Number *= amorCoeff frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } nRate := float64(int((frac.Number * cost.Number * rate.Number) + 0.5)) cost.Number -= nRate rest := cost.Number - salvage.Number for n := 0; n < int(period.Number); n++ { nRate = float64(int((cost.Number * rate.Number) + 0.5)) rest -= nRate if rest < 0 { switch int(period.Number) - n { case 0: case 1: return newNumberFormulaArg(float64(int((cost.Number * 0.5) + 0.5))) default: return newNumberFormulaArg(0) } } cost.Number -= nRate } return newNumberFormulaArg(nRate) } // AMORLINC function is provided for users of the French accounting system. // The function calculates the prorated linear depreciation of an asset for a // specified accounting period. The syntax of the function is: // // AMORLINC(cost,date_purchased,first_period,salvage,period,rate,[basis]) func (fn *formulaFuncs) AMORLINC(argsList *list.List) formulaArg { if argsList.Len() != 6 && argsList.Len() != 7 { return newErrorFormulaArg(formulaErrorVALUE, "AMORLINC requires 6 or 7 arguments") } args := fn.prepareAmorArgs("AMORLINC", argsList) if args.Type != ArgList { return args } cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6] frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } rate1 := frac.Number * cost.Number * rate.Number if period.Number == 0 { return newNumberFormulaArg(rate1) } rate2 := cost.Number * rate.Number delta := cost.Number - salvage.Number periods := int((delta - rate1) / rate2) if int(period.Number) <= periods { return newNumberFormulaArg(rate2) } else if int(period.Number)-1 == periods { return newNumberFormulaArg(delta - rate2*float64(periods) - math.Nextafter(rate1, rate1)) } return newNumberFormulaArg(0) } // prepareCouponArgs checking and prepare arguments for the formula functions // COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPPCD, COUPNUM and COUPNCD. func (fn *formulaFuncs) prepareCouponArgs(name string, argsList *list.List) formulaArg { if argsList.Len() != 3 && argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 or 4 arguments", name)) } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] if settlement.Number >= maturity.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) } frequency := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if frequency.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 4 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newListFormulaArg([]formulaArg{settlement, maturity, frequency, basis}) } // is30BasisMethod determine if the financial day count basis rules is 30/360 // methods. func is30BasisMethod(basis int) bool { return basis == 0 || basis == 4 } // getDaysInMonthRange return the day by given year, month range and day count // basis. func getDaysInMonthRange(fromMonth, toMonth int) int { if fromMonth > toMonth { return 0 } return (toMonth - fromMonth + 1) * 30 } // getDayOnBasis returns the day by given date and day count basis. func getDayOnBasis(y, m, d, basis int) int { if !is30BasisMethod(basis) { return d } day := d dim := getDaysInMonth(y, m) if day > 30 || d >= dim || day >= dim { day = 30 } return day } // coupdays returns the number of days that base on date range and the day // count basis to be used. func coupdays(from, to time.Time, basis int) float64 { days := 0 fromY, fromM, fromD := from.Date() toY, toM, toD := to.Date() fromDay, toDay := getDayOnBasis(fromY, int(fromM), fromD, basis), getDayOnBasis(toY, int(toM), toD, basis) if !is30BasisMethod(basis) { return (daysBetween(excelMinTime1900.Unix(), makeDate(toY, toM, toDay)) + 1) - (daysBetween(excelMinTime1900.Unix(), makeDate(fromY, fromM, fromDay)) + 1) } if basis == 0 { if (int(fromM) == 2 || fromDay < 30) && toD == 31 { toDay = 31 } } else { if int(fromM) == 2 && fromDay == 30 { fromDay = getDaysInMonth(fromY, 2) } if int(toM) == 2 && toDay == 30 { toDay = getDaysInMonth(toY, 2) } } if fromY < toY || (fromY == toY && int(fromM) < int(toM)) { days = 30 - fromDay + 1 fromD = 1 fromDay = 1 date := time.Date(fromY, fromM, fromD, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0) if date.Year() < toY { days += getDaysInMonthRange(int(date.Month()), 12) date = date.AddDate(0, 13-int(date.Month()), 0) } days += getDaysInMonthRange(int(date.Month()), int(toM)-1) } if days += toDay - fromDay; days > 0 { return float64(days) } return 0 } // COUPDAYBS function calculates the number of days from the beginning of a // coupon's period to the settlement date. The syntax of the function is: // // COUPDAYBS(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPDAYBS(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPDAYBS", argsList) if args.Type != ArgList { return args } settlement := timeFromExcelTime(args.List[0].Number, false) pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false) return newNumberFormulaArg(coupdays(pcd, settlement, int(args.List[3].Number))) } // COUPDAYS function calculates the number of days in a coupon period that // contains the settlement date. The syntax of the function is: // // COUPDAYS(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPDAYS(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPDAYS", argsList) if args.Type != ArgList { return args } freq := args.List[2].Number basis := int(args.List[3].Number) if basis == 1 { pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false) next := pcd.AddDate(0, 12/int(freq), 0) return newNumberFormulaArg(coupdays(pcd, next, basis)) } return newNumberFormulaArg(float64(getYearDays(0, basis)) / freq) } // COUPDAYSNC function calculates the number of days from the settlement date // to the next coupon date. The syntax of the function is: // // COUPDAYSNC(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPDAYSNC", argsList) if args.Type != ArgList { return args } settlement := timeFromExcelTime(args.List[0].Number, false) basis := int(args.List[3].Number) ncd := timeFromExcelTime(fn.COUPNCD(argsList).Number, false) return newNumberFormulaArg(coupdays(settlement, ncd, basis)) } // coupons is an implementation of the formula functions COUPNCD and COUPPCD. func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg { settlement := timeFromExcelTime(arg.List[0].Number, false) maturity := timeFromExcelTime(arg.List[1].Number, false) maturityDays := (maturity.Year()-settlement.Year())*12 + (int(maturity.Month()) - int(settlement.Month())) coupon := 12 / int(arg.List[2].Number) mod := maturityDays % coupon year := settlement.Year() month := int(settlement.Month()) if mod == 0 && settlement.Day() >= maturity.Day() { month += coupon } else { month += mod } if name != "COUPNCD" { month -= coupon } if month > 11 { year++ month -= 12 } else if month < 0 { year-- month += 12 } day, lastDay := maturity.Day(), time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) days := getDaysInMonth(lastDay.Year(), int(lastDay.Month())) if getDaysInMonth(maturity.Year(), int(maturity.Month())) == maturity.Day() { day = days } else if day > 27 && day > days { day = days } return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(year, time.Month(month), day)) + 1) } // COUPNCD function calculates the number of coupons payable, between a // security's settlement date and maturity date, rounded up to the nearest // whole coupon. The syntax of the function is: // // COUPNCD(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPNCD(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPNCD", argsList) if args.Type != ArgList { return args } return fn.coupons("COUPNCD", args) } // COUPNUM function calculates the number of coupons payable, between a // security's settlement date and maturity date, rounded up to the nearest // whole coupon. The syntax of the function is: // // COUPNUM(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPNUM(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPNUM", argsList) if args.Type != ArgList { return args } frac := yearFrac(args.List[0].Number, args.List[1].Number, 0) return newNumberFormulaArg(math.Ceil(frac.Number * args.List[2].Number)) } // COUPPCD function returns the previous coupon date, before the settlement // date for a security. The syntax of the function is: // // COUPPCD(settlement,maturity,frequency,[basis]) func (fn *formulaFuncs) COUPPCD(argsList *list.List) formulaArg { args := fn.prepareCouponArgs("COUPPCD", argsList) if args.Type != ArgList { return args } return fn.coupons("COUPPCD", args) } // CUMIPMT function calculates the cumulative interest paid on a loan or // investment, between two specified periods. The syntax of the function is: // // CUMIPMT(rate,nper,pv,start_period,end_period,type) func (fn *formulaFuncs) CUMIPMT(argsList *list.List) formulaArg { return fn.cumip("CUMIPMT", argsList) } // CUMPRINC function calculates the cumulative payment on the principal of a // loan or investment, between two specified periods. The syntax of the // function is: // // CUMPRINC(rate,nper,pv,start_period,end_period,type) func (fn *formulaFuncs) CUMPRINC(argsList *list.List) formulaArg { return fn.cumip("CUMPRINC", argsList) } // cumip is an implementation of the formula functions CUMIPMT and CUMPRINC. func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg { if argsList.Len() != 6 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 arguments", name)) } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } nper := argsList.Front().Next().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } start := argsList.Back().Prev().Prev().Value.(formulaArg).ToNumber() if start.Type != ArgNumber { return start } end := argsList.Back().Prev().Value.(formulaArg).ToNumber() if end.Type != ArgNumber { return end } typ := argsList.Back().Value.(formulaArg).ToNumber() if typ.Type != ArgNumber { return typ } if typ.Number != 0 && typ.Number != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if start.Number < 1 || start.Number > end.Number { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } num := 0.0 for per := start.Number; per <= end.Number; per++ { args := list.New().Init() args.PushBack(rate) args.PushBack(newNumberFormulaArg(per)) args.PushBack(nper) args.PushBack(pv) args.PushBack(newNumberFormulaArg(0)) args.PushBack(typ) if name == "CUMIPMT" { num += fn.IPMT(args).Number continue } num += fn.PPMT(args).Number } return newNumberFormulaArg(num) } // calcDbArgsCompare implements common arguments' comparison for DB and DDB. func calcDbArgsCompare(cost, salvage, life, period formulaArg) bool { return (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) } // DB function calculates the depreciation of an asset, using the Fixed // Declining Balance Method, for each period of the asset's lifetime. The // syntax of the function is: // // DB(cost,salvage,life,period,[month]) func (fn *formulaFuncs) DB(argsList *list.List) formulaArg { if argsList.Len() < 4 { return newErrorFormulaArg(formulaErrorVALUE, "DB requires at least 4 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "DB allows at most 5 arguments") } cost := argsList.Front().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber { return cost } salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() if salvage.Type != ArgNumber { return salvage } life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if life.Type != ArgNumber { return life } period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if period.Type != ArgNumber { return period } month := newNumberFormulaArg(12) if argsList.Len() == 5 { if month = argsList.Back().Value.(formulaArg).ToNumber(); month.Type != ArgNumber { return month } } if cost.Number == 0 { return newNumberFormulaArg(0) } if calcDbArgsCompare(cost, salvage, life, period) || (month.Number < 1) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } dr := 1 - math.Pow(salvage.Number/cost.Number, 1/life.Number) dr = math.Round(dr*1000) / 1000 pd, depreciation := 0.0, 0.0 for per := 1; per <= int(period.Number); per++ { if per == 1 { depreciation = cost.Number * dr * month.Number / 12 } else if per == int(life.Number+1) { depreciation = (cost.Number - pd) * dr * (12 - month.Number) / 12 } else { depreciation = (cost.Number - pd) * dr } pd += depreciation } return newNumberFormulaArg(depreciation) } // DDB function calculates the depreciation of an asset, using the Double // Declining Balance Method, or another specified depreciation rate. The // syntax of the function is: // // DDB(cost,salvage,life,period,[factor]) func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg { if argsList.Len() < 4 { return newErrorFormulaArg(formulaErrorVALUE, "DDB requires at least 4 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "DDB allows at most 5 arguments") } cost := argsList.Front().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber { return cost } salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() if salvage.Type != ArgNumber { return salvage } life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if life.Type != ArgNumber { return life } period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if period.Type != ArgNumber { return period } factor := newNumberFormulaArg(2) if argsList.Len() == 5 { if factor = argsList.Back().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber { return factor } } if cost.Number == 0 { return newNumberFormulaArg(0) } if calcDbArgsCompare(cost, salvage, life, period) || (factor.Number <= 0.0) || (period.Number > life.Number) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } pd, depreciation := 0.0, 0.0 for per := 1; per <= int(period.Number); per++ { depreciation = math.Min((cost.Number-pd)*(factor.Number/life.Number), cost.Number-salvage.Number-pd) pd += depreciation } return newNumberFormulaArg(depreciation) } // prepareDataValueArgs convert first N arguments to data value for the // formula functions. func (fn *formulaFuncs) prepareDataValueArgs(n int, argsList *list.List) formulaArg { l := list.New() var dataValues []formulaArg getDateValue := func(arg formulaArg, l *list.List) formulaArg { switch arg.Type { case ArgNumber: break case ArgString: num := arg.ToNumber() if num.Type == ArgNumber { arg = num break } l.Init() l.PushBack(arg) arg = fn.DATEVALUE(l) if arg.Type == ArgError { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } default: return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } return arg } for i, arg := 0, argsList.Front(); i < n; arg = arg.Next() { dataValue := getDateValue(arg.Value.(formulaArg), l) if dataValue.Type != ArgNumber { return dataValue } dataValues = append(dataValues, dataValue) i++ } return newListFormulaArg(dataValues) } // discIntrate is an implementation of the formula functions DISC and INTRATE. func (fn *formulaFuncs) discIntrate(name string, argsList *list.List) formulaArg { if argsList.Len() != 4 && argsList.Len() != 5 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 or 5 arguments", name)) } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity, argName := args.List[0], args.List[1], "pr" if maturity.Number <= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) } prInvestment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if prInvestment.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if prInvestment.Number <= 0 { if name == "INTRATE" { argName = "investment" } return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires %s > 0", name, argName)) } redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if redemption.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name)) } basis := newNumberFormulaArg(0) if argsList.Len() == 5 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } if name == "INTRATE" { return newNumberFormulaArg((redemption.Number - prInvestment.Number) / prInvestment.Number / frac.Number) } return newNumberFormulaArg((redemption.Number - prInvestment.Number) / redemption.Number / frac.Number) } // DISC function calculates the Discount Rate for a security. The syntax of // the function is: // // DISC(settlement,maturity,pr,redemption,[basis]) func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg { return fn.discIntrate("DISC", argsList) } // DOLLAR function rounds a supplied number to a specified number of decimal // places and then converts this into a text string with a currency format. The // syntax of the function is: // // DOLLAR(number,[decimals]) func (fn *formulaFuncs) DOLLAR(argsList *list.List) formulaArg { if argsList.Len() == 0 { return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires 1 or 2 arguments") } numArg := argsList.Front().Value.(formulaArg) n := numArg.ToNumber() if n.Type != ArgNumber { return n } decimals, dot, value := 2, ".", numArg.Value() if argsList.Len() == 2 { d := argsList.Back().Value.(formulaArg).ToNumber() if d.Type != ArgNumber { return d } if d.Number < 0 { value = strconv.FormatFloat(fn.round(n.Number, d.Number, down), 'f', -1, 64) } if d.Number >= 128 { return newErrorFormulaArg(formulaErrorVALUE, "decimal value should be less than 128") } if decimals = int(d.Number); decimals < 0 { decimals, dot = 0, "" } } symbol := map[CultureName]string{ CultureNameUnknown: "$", CultureNameEnUS: "$", CultureNameJaJP: "¥", CultureNameKoKR: "\u20a9", CultureNameZhCN: "¥", CultureNameZhTW: "NT$", }[fn.f.options.CultureInfo] numFmtCode := fmt.Sprintf("%s#,##0%s%s;(%s#,##0%s%s)", symbol, dot, strings.Repeat("0", decimals), symbol, dot, strings.Repeat("0", decimals)) return newStringFormulaArg(format(value, numFmtCode, false, CellTypeNumber, nil)) } // DOLLARDE function converts a dollar value in fractional notation, into a // dollar value expressed as a decimal. The syntax of the function is: // // DOLLARDE(fractional_dollar,fraction) func (fn *formulaFuncs) DOLLARDE(argsList *list.List) formulaArg { return fn.dollar("DOLLARDE", argsList) } // DOLLARFR function converts a dollar value in decimal notation, into a // dollar value that is expressed in fractional notation. The syntax of the // function is: // // DOLLARFR(decimal_dollar,fraction) func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg { return fn.dollar("DOLLARFR", argsList) } // dollar is an implementation of the formula functions DOLLARDE and DOLLARFR. func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) } dollar := argsList.Front().Value.(formulaArg).ToNumber() if dollar.Type != ArgNumber { return dollar } frac := argsList.Back().Value.(formulaArg).ToNumber() if frac.Type != ArgNumber { return frac } if frac.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if frac.Number == 0 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } cents := math.Mod(dollar.Number, 1) if name == "DOLLARDE" { cents /= frac.Number cents *= math.Pow(10, math.Ceil(math.Log10(frac.Number))) } else { cents *= frac.Number cents *= math.Pow(10, -math.Ceil(math.Log10(frac.Number))) } return newNumberFormulaArg(math.Floor(dollar.Number) + cents) } // prepareDurationArgs checking and prepare arguments for the formula // functions DURATION and MDURATION. func (fn *formulaFuncs) prepareDurationArgs(name string, argsList *list.List) formulaArg { if argsList.Len() != 5 && argsList.Len() != 6 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 5 or 6 arguments", name)) } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] if settlement.Number >= maturity.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) } coupon := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if coupon.Type != ArgNumber { return coupon } if coupon.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires coupon >= 0", name)) } yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if yld.Type != ArgNumber { return yld } if yld.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name)) } frequency := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if frequency.Type != ArgNumber { return frequency } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 6 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newListFormulaArg([]formulaArg{settlement, maturity, coupon, yld, frequency, basis}) } // duration is an implementation of the formula function DURATION. func (fn *formulaFuncs) duration(settlement, maturity, coupon, yld, frequency, basis formulaArg) formulaArg { frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } argumments := list.New().Init() argumments.PushBack(settlement) argumments.PushBack(maturity) argumments.PushBack(frequency) argumments.PushBack(basis) coups := fn.COUPNUM(argumments) duration := 0.0 p := 0.0 coupon.Number *= 100 / frequency.Number yld.Number /= frequency.Number yld.Number++ diff := frac.Number*frequency.Number - coups.Number for t := 1.0; t < coups.Number; t++ { tDiff := t + diff add := coupon.Number / math.Pow(yld.Number, tDiff) p += add duration += tDiff * add } add := (coupon.Number + 100) / math.Pow(yld.Number, coups.Number+diff) p += add duration += (coups.Number + diff) * add duration /= p duration /= frequency.Number return newNumberFormulaArg(duration) } // DURATION function calculates the Duration (specifically, the Macaulay // Duration) of a security that pays periodic interest, assuming a par value // of $100. The syntax of the function is: // // DURATION(settlement,maturity,coupon,yld,frequency,[basis]) func (fn *formulaFuncs) DURATION(argsList *list.List) formulaArg { args := fn.prepareDurationArgs("DURATION", argsList) if args.Type != ArgList { return args } return fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]) } // EFFECT function returns the effective annual interest rate for a given // nominal interest rate and number of compounding periods per year. The // syntax of the function is: // // EFFECT(nominal_rate,npery) func (fn *formulaFuncs) EFFECT(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "EFFECT requires 2 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } npery := argsList.Back().Value.(formulaArg).ToNumber() if npery.Type != ArgNumber { return npery } if rate.Number <= 0 || npery.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(math.Pow(1+rate.Number/npery.Number, npery.Number) - 1) } // EUROCONVERT function convert a number to euro or from euro to a // participating currency. You can also use it to convert a number from one // participating currency to another by using the euro as an intermediary // (triangulation). The syntax of the function is: // // EUROCONVERT(number,sourcecurrency,targetcurrency[,fullprecision,triangulationprecision]) func (fn *formulaFuncs) EUROCONVERT(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "EUROCONVERT requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "EUROCONVERT allows at most 5 arguments") } number := argsList.Front().Value.(formulaArg).ToNumber() if number.Type != ArgNumber { return number } sourceCurrency := argsList.Front().Next().Value.(formulaArg).Value() targetCurrency := argsList.Front().Next().Next().Value.(formulaArg).Value() fullPrec, triangulationPrec := newBoolFormulaArg(false), newNumberFormulaArg(0) if argsList.Len() >= 4 { if fullPrec = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool(); fullPrec.Type != ArgNumber { return fullPrec } } if argsList.Len() == 5 { if triangulationPrec = argsList.Back().Value.(formulaArg).ToNumber(); triangulationPrec.Type != ArgNumber { return triangulationPrec } } convertTable := map[string][]float64{ "EUR": {1.0, 2}, "ATS": {13.7603, 2}, "BEF": {40.3399, 0}, "DEM": {1.95583, 2}, "ESP": {166.386, 0}, "FIM": {5.94573, 2}, "FRF": {6.55957, 2}, "IEP": {0.787564, 2}, "ITL": {1936.27, 0}, "LUF": {40.3399, 0}, "NLG": {2.20371, 2}, "PTE": {200.482, 2}, "GRD": {340.750, 2}, "SIT": {239.640, 2}, "MTL": {0.429300, 2}, "CYP": {0.585274, 2}, "SKK": {30.1260, 2}, "EEK": {15.6466, 2}, "LVL": {0.702804, 2}, "LTL": {3.45280, 2}, } source, ok := convertTable[sourceCurrency] if !ok { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } target, ok := convertTable[targetCurrency] if !ok { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if sourceCurrency == targetCurrency { return number } var res float64 if sourceCurrency == "EUR" { res = number.Number * target[0] } else { intermediate := number.Number / source[0] if triangulationPrec.Number != 0 { ratio := math.Pow(10, triangulationPrec.Number) intermediate = math.Round(intermediate*ratio) / ratio } res = intermediate * target[0] } if fullPrec.Number != 1 { ratio := math.Pow(10, target[1]) res = math.Round(res*ratio) / ratio } return newNumberFormulaArg(res) } // FV function calculates the Future Value of an investment with periodic // constant payments and a constant interest rate. The syntax of the function // is: // // FV(rate,nper,[pmt],[pv],[type]) func (fn *formulaFuncs) FV(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "FV requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "FV allows at most 5 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } nper := argsList.Front().Next().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pmt.Type != ArgNumber { return pmt } pv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) if argsList.Len() >= 4 { if pv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); pv.Type != ArgNumber { return pv } } if argsList.Len() == 5 { if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { return typ } } if typ.Number != 0 && typ.Number != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if rate.Number != 0 { return newNumberFormulaArg(-pv.Number*math.Pow(1+rate.Number, nper.Number) - pmt.Number*(1+rate.Number*typ.Number)*(math.Pow(1+rate.Number, nper.Number)-1)/rate.Number) } return newNumberFormulaArg(-pv.Number - pmt.Number*nper.Number) } // FVSCHEDULE function calculates the Future Value of an investment with a // variable interest rate. The syntax of the function is: // // FVSCHEDULE(principal,schedule) func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "FVSCHEDULE requires 2 arguments") } pri := argsList.Front().Value.(formulaArg).ToNumber() if pri.Type != ArgNumber { return pri } principal := pri.Number for _, arg := range argsList.Back().Value.(formulaArg).ToList() { if arg.Value() == "" { continue } rate := arg.ToNumber() if rate.Type != ArgNumber { return rate } principal *= 1 + rate.Number } return newNumberFormulaArg(principal) } // INTRATE function calculates the interest rate for a fully invested // security. The syntax of the function is: // // INTRATE(settlement,maturity,investment,redemption,[basis]) func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg { return fn.discIntrate("INTRATE", argsList) } // IPMT function calculates the interest payment, during a specific period of a // loan or investment that is paid in constant periodic payments, with a // constant interest rate. The syntax of the function is: // // IPMT(rate,per,nper,pv,[fv],[type]) func (fn *formulaFuncs) IPMT(argsList *list.List) formulaArg { return fn.ipmt("IPMT", argsList) } // calcIpmt is part of the implementation ipmt. func calcIpmt(name string, typ, per, pmt, pv, rate formulaArg) formulaArg { capital, interest, principal := pv.Number, 0.0, 0.0 for i := 1; i <= int(per.Number); i++ { if typ.Number != 0 && i == 1 { interest = 0 } else { interest = -capital * rate.Number } principal = pmt.Number - interest capital += principal } if name == "IPMT" { return newNumberFormulaArg(interest) } return newNumberFormulaArg(principal) } // ipmt is an implementation of the formula functions IPMT and PPMT. func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg { if argsList.Len() < 4 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 4 arguments", name)) } if argsList.Len() > 6 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 6 arguments", name)) } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } per := argsList.Front().Next().Value.(formulaArg).ToNumber() if per.Type != ArgNumber { return per } nper := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pv := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) if argsList.Len() >= 5 { if fv = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { return fv } } if argsList.Len() == 6 { if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { return typ } } if typ.Number != 0 && typ.Number != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if per.Number <= 0 || per.Number > nper.Number { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } args := list.New().Init() args.PushBack(rate) args.PushBack(nper) args.PushBack(pv) args.PushBack(fv) args.PushBack(typ) pmt := fn.PMT(args) return calcIpmt(name, typ, per, pmt, pv, rate) } // IRR function returns the Internal Rate of Return for a supplied series of // periodic cash flows (i.e. an initial investment value and a series of net // income values). The syntax of the function is: // // IRR(values,[guess]) func (fn *formulaFuncs) IRR(argsList *list.List) formulaArg { if argsList.Len() < 1 { return newErrorFormulaArg(formulaErrorVALUE, "IRR requires at least 1 argument") } if argsList.Len() > 2 { return newErrorFormulaArg(formulaErrorVALUE, "IRR allows at most 2 arguments") } values, guess := argsList.Front().Value.(formulaArg).ToList(), newNumberFormulaArg(0.1) if argsList.Len() > 1 { if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { return guess } } x1, x2 := newNumberFormulaArg(0), guess args := list.New().Init() args.PushBack(x1) for _, v := range values { args.PushBack(v) } f1 := fn.NPV(args) args.Front().Value = x2 f2 := fn.NPV(args) for i := 0; i < maxFinancialIterations; i++ { if f1.Number*f2.Number < 0 { break } if math.Abs(f1.Number) < math.Abs(f2.Number) { x1.Number += 1.6 * (x1.Number - x2.Number) args.Front().Value = x1 f1 = fn.NPV(args) continue } x2.Number += 1.6 * (x2.Number - x1.Number) args.Front().Value = x2 f2 = fn.NPV(args) } if f1.Number*f2.Number > 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } args.Front().Value = x1 f := fn.NPV(args) var rtb, dx, xMid, fMid float64 if f.Number < 0 { rtb = x1.Number dx = x2.Number - x1.Number } else { rtb = x2.Number dx = x1.Number - x2.Number } for i := 0; i < maxFinancialIterations; i++ { dx *= 0.5 xMid = rtb + dx args.Front().Value = newNumberFormulaArg(xMid) fMid = fn.NPV(args).Number if fMid <= 0 { rtb = xMid } if math.Abs(fMid) < financialPrecision || math.Abs(dx) < financialPrecision { break } } return newNumberFormulaArg(xMid) } // ISPMT function calculates the interest paid during a specific period of a // loan or investment. The syntax of the function is: // // ISPMT(rate,per,nper,pv) func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "ISPMT requires 4 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } per := argsList.Front().Next().Value.(formulaArg).ToNumber() if per.Type != ArgNumber { return per } nper := argsList.Back().Prev().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pv := argsList.Back().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } pr, payment, num := pv.Number, pv.Number/nper.Number, 0.0 for i := 0; i <= int(per.Number); i++ { num = rate.Number * pr * -1 pr -= payment if i == int(nper.Number) { num = 0 } } return newNumberFormulaArg(num) } // MDURATION function calculates the Modified Macaulay Duration of a security // that pays periodic interest, assuming a par value of $100. The syntax of // the function is: // // MDURATION(settlement,maturity,coupon,yld,frequency,[basis]) func (fn *formulaFuncs) MDURATION(argsList *list.List) formulaArg { args := fn.prepareDurationArgs("MDURATION", argsList) if args.Type != ArgList { return args } duration := fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]) if duration.Type != ArgNumber { return duration } return newNumberFormulaArg(duration.Number / (1 + args.List[3].Number/args.List[4].Number)) } // MIRR function returns the Modified Internal Rate of Return for a supplied // series of periodic cash flows (i.e. a set of values, which includes an // initial investment value and a series of net income values). The syntax of // the function is: // // MIRR(values,finance_rate,reinvest_rate) func (fn *formulaFuncs) MIRR(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "MIRR requires 3 arguments") } values := argsList.Front().Value.(formulaArg).ToList() financeRate := argsList.Front().Next().Value.(formulaArg).ToNumber() if financeRate.Type != ArgNumber { return financeRate } reinvestRate := argsList.Back().Value.(formulaArg).ToNumber() if reinvestRate.Type != ArgNumber { return reinvestRate } n, fr, rr, npvPos, npvNeg := len(values), 1+financeRate.Number, 1+reinvestRate.Number, 0.0, 0.0 for i, v := range values { val := v.ToNumber() if val.Number >= 0 { npvPos += val.Number / math.Pow(rr, float64(i)) continue } npvNeg += val.Number / math.Pow(fr, float64(i)) } if npvNeg == 0 || npvPos == 0 || reinvestRate.Number <= -1 { return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } return newNumberFormulaArg(math.Pow(-npvPos*math.Pow(rr, float64(n))/(npvNeg*rr), 1/(float64(n)-1)) - 1) } // NOMINAL function returns the nominal interest rate for a given effective // interest rate and number of compounding periods per year. The syntax of // the function is: // // NOMINAL(effect_rate,npery) func (fn *formulaFuncs) NOMINAL(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "NOMINAL requires 2 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } npery := argsList.Back().Value.(formulaArg).ToNumber() if npery.Type != ArgNumber { return npery } if rate.Number <= 0 || npery.Number < 1 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(npery.Number * (math.Pow(rate.Number+1, 1/npery.Number) - 1)) } // NPER function calculates the number of periods required to pay off a loan, // for a constant periodic payment and a constant interest rate. The syntax // of the function is: // // NPER(rate,pmt,pv,[fv],[type]) func (fn *formulaFuncs) NPER(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "NPER requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "NPER allows at most 5 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } pmt := argsList.Front().Next().Value.(formulaArg).ToNumber() if pmt.Type != ArgNumber { return pmt } pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) if argsList.Len() >= 4 { if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { return fv } } if argsList.Len() == 5 { if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { return typ } } if typ.Number != 0 && typ.Number != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if pmt.Number == 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if rate.Number != 0 { p := math.Log((pmt.Number*(1+rate.Number*typ.Number)/rate.Number-fv.Number)/(pv.Number+pmt.Number*(1+rate.Number*typ.Number)/rate.Number)) / math.Log(1+rate.Number) return newNumberFormulaArg(p) } return newNumberFormulaArg((-pv.Number - fv.Number) / pmt.Number) } // NPV function calculates the Net Present Value of an investment, based on a // supplied discount rate, and a series of future payments and income. The // syntax of the function is: // // NPV(rate,value1,[value2],[value3],...) func (fn *formulaFuncs) NPV(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "NPV requires at least 2 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } val, i := 0.0, 1 for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { num := arg.Value.(formulaArg).ToNumber() if num.Type != ArgNumber { continue } val += num.Number / math.Pow(1+rate.Number, float64(i)) i++ } return newNumberFormulaArg(val) } // aggrBetween is a part of implementation of the formula function ODDFPRICE. func aggrBetween(startPeriod, endPeriod float64, initialValue []float64, f func(acc []float64, index float64) []float64) []float64 { var s []float64 if startPeriod <= endPeriod { for i := startPeriod; i <= endPeriod; i++ { s = append(s, i) } } else { for i := startPeriod; i >= endPeriod; i-- { s = append(s, i) } } return fold(f, initialValue, s) } // fold is a part of implementation of the formula function ODDFPRICE. func fold(f func(acc []float64, index float64) []float64, state []float64, source []float64) []float64 { length, value := len(source), state for index := 0; length > index; index++ { value = f(value, source[index]) } return value } // changeMonth is a part of implementation of the formula function ODDFPRICE. func changeMonth(date time.Time, numMonths float64, returnLastMonth bool) time.Time { offsetDay := 0 if returnLastMonth && date.Day() == getDaysInMonth(date.Year(), int(date.Month())) { offsetDay-- } newDate := date.AddDate(0, int(numMonths), offsetDay) if returnLastMonth { lastDay := getDaysInMonth(newDate.Year(), int(newDate.Month())) return timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), makeDate(newDate.Year(), newDate.Month(), lastDay))+1, false) } return newDate } // datesAggregate is a part of implementation of the formula function // ODDFPRICE. func datesAggregate(startDate, endDate time.Time, numMonths float64, f func(pcd, ncd time.Time) float64, acc float64, returnLastMonth bool) (time.Time, time.Time, float64) { frontDate, trailingDate := startDate, endDate s1 := frontDate.After(endDate) || frontDate.Equal(endDate) s2 := endDate.After(frontDate) || endDate.Equal(frontDate) stop := s2 if numMonths > 0 { stop = s1 } for !stop { trailingDate = frontDate frontDate = changeMonth(frontDate, numMonths, returnLastMonth) fn := f(frontDate, trailingDate) acc += fn s1 = frontDate.After(endDate) || frontDate.Equal(endDate) s2 = endDate.After(frontDate) || endDate.Equal(frontDate) stop = s2 if numMonths > 0 { stop = s1 } } return frontDate, trailingDate, acc } // coupNumber is a part of implementation of the formula function ODDFPRICE. func coupNumber(maturity, settlement, numMonths float64) float64 { maturityTime, settlementTime := timeFromExcelTime(maturity, false), timeFromExcelTime(settlement, false) my, mm, md := maturityTime.Year(), maturityTime.Month(), maturityTime.Day() sy, sm, sd := settlementTime.Year(), settlementTime.Month(), settlementTime.Day() couponsTemp, endOfMonthTemp := 0.0, getDaysInMonth(my, int(mm)) == md endOfMonth := endOfMonthTemp if !endOfMonthTemp && mm != 2 && md > 28 && md < getDaysInMonth(my, int(mm)) { endOfMonth = getDaysInMonth(sy, int(sm)) == sd } startDate := changeMonth(settlementTime, 0, endOfMonth) coupons := couponsTemp if startDate.After(settlementTime) { coupons++ } date := changeMonth(startDate, numMonths, endOfMonth) f := func(pcd, ncd time.Time) float64 { return 1 } _, _, result := datesAggregate(date, maturityTime, numMonths, f, coupons, endOfMonth) return result } // prepareOddYldOrPrArg checking and prepare yield or price arguments for the // formula functions ODDFPRICE, ODDFYIELD, ODDLPRICE and ODDLYIELD. func prepareOddYldOrPrArg(name string, arg formulaArg) formulaArg { yldOrPr := arg.ToNumber() if yldOrPr.Type != ArgNumber { return yldOrPr } if (name == "ODDFPRICE" || name == "ODDLPRICE") && yldOrPr.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name)) } if (name == "ODDFYIELD" || name == "ODDLYIELD") && yldOrPr.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires pr > 0", name)) } return yldOrPr } // prepareOddfArgs checking and prepare arguments for the formula // functions ODDFPRICE and ODDFYIELD. func (fn *formulaFuncs) prepareOddfArgs(name string, argsList *list.List) formulaArg { dateValues := fn.prepareDataValueArgs(4, argsList) if dateValues.Type != ArgList { return dateValues } settlement, maturity, issue, firstCoupon := dateValues.List[0], dateValues.List[1], dateValues.List[2], dateValues.List[3] if issue.Number >= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > issue", name)) } if settlement.Number >= firstCoupon.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires first_coupon > settlement", name)) } if firstCoupon.Number >= maturity.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > first_coupon", name)) } rate := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } if rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name)) } yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg)) if yldOrPr.Type != ArgNumber { return yldOrPr } redemption := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if redemption.Type != ArgNumber { return redemption } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name)) } frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if frequency.Type != ArgNumber { return frequency } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 9 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newListFormulaArg([]formulaArg{settlement, maturity, issue, firstCoupon, rate, yldOrPr, redemption, frequency, basis}) } // ODDFPRICE function calculates the price per $100 face value of a security // with an odd (short or long) first period. The syntax of the function is: // // ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,[basis]) func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg { if argsList.Len() != 8 && argsList.Len() != 9 { return newErrorFormulaArg(formulaErrorVALUE, "ODDFPRICE requires 8 or 9 arguments") } args := fn.prepareOddfArgs("ODDFPRICE", argsList) if args.Type != ArgList { return args } settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8] if basisArg.Number < 0 || basisArg.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, "invalid basis") } issueTime := timeFromExcelTime(issue.Number, false) settlementTime := timeFromExcelTime(settlement.Number, false) maturityTime := timeFromExcelTime(maturity.Number, false) firstCouponTime := timeFromExcelTime(firstCoupon.Number, false) basis := int(basisArg.Number) monthDays := getDaysInMonth(maturityTime.Year(), int(maturityTime.Month())) returnLastMonth := monthDays == maturityTime.Day() numMonths := 12 / frequency.Number numMonthsNeg := -numMonths mat := changeMonth(maturityTime, numMonthsNeg, returnLastMonth) pcd, _, _ := datesAggregate(mat, firstCouponTime, numMonthsNeg, func(d1, d2 time.Time) float64 { return 0 }, 0, returnLastMonth) if !pcd.Equal(firstCouponTime) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } fnArgs := list.New().Init() fnArgs.PushBack(settlement) fnArgs.PushBack(maturity) fnArgs.PushBack(frequency) fnArgs.PushBack(basisArg) e := fn.COUPDAYS(fnArgs) n := fn.COUPNUM(fnArgs) m := frequency.Number dfc := coupdays(issueTime, firstCouponTime, basis) if dfc < e.Number { dsc := coupdays(settlementTime, firstCouponTime, basis) a := coupdays(issueTime, settlementTime, basis) x := yld.Number/m + 1 y := dsc / e.Number p1 := x p3 := math.Pow(p1, n.Number-1+y) term1 := redemption.Number / p3 term2 := 100 * rate.Number / m * dfc / e.Number / math.Pow(p1, y) f := func(acc []float64, index float64) []float64 { return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index-1+y)} } term3 := aggrBetween(2, math.Floor(n.Number), []float64{0}, f) p2 := rate.Number / m term4 := a / e.Number * p2 * 100 return newNumberFormulaArg(term1 + term2 + term3[0] - term4) } fnArgs.Init() fnArgs.PushBack(issue) fnArgs.PushBack(firstCoupon) fnArgs.PushBack(frequency) nc := fn.COUPNUM(fnArgs) lastCoupon := firstCoupon.Number aggrFunc := func(acc []float64, index float64) []float64 { lastCouponTime := timeFromExcelTime(lastCoupon, false) earlyCoupon := daysBetween(excelMinTime1900.Unix(), makeDate(lastCouponTime.Year(), time.Month(float64(lastCouponTime.Month())+numMonthsNeg), lastCouponTime.Day())) + 1 earlyCouponTime := timeFromExcelTime(earlyCoupon, false) nl := e.Number if basis == 1 { nl = coupdays(earlyCouponTime, lastCouponTime, basis) } dci := coupdays(issueTime, lastCouponTime, basis) if index > 1 { dci = nl } startDate := earlyCoupon if issue.Number > earlyCoupon { startDate = issue.Number } endDate := lastCoupon if settlement.Number < lastCoupon { endDate = settlement.Number } startDateTime := timeFromExcelTime(startDate, false) endDateTime := timeFromExcelTime(endDate, false) a := coupdays(startDateTime, endDateTime, basis) lastCoupon = earlyCoupon dcnl := acc[0] anl := acc[1] return []float64{dcnl + dci/nl, anl + a/nl} } ag := aggrBetween(math.Floor(nc.Number), 1, []float64{0, 0}, aggrFunc) dcnl, anl := ag[0], ag[1] dsc := 0.0 fnArgs.Init() fnArgs.PushBack(settlement) fnArgs.PushBack(firstCoupon) fnArgs.PushBack(frequency) if basis == 2 || basis == 3 { d := timeFromExcelTime(fn.COUPNCD(fnArgs).Number, false) dsc = coupdays(settlementTime, d, basis) } else { d := timeFromExcelTime(fn.COUPPCD(fnArgs).Number, false) a := coupdays(d, settlementTime, basis) dsc = e.Number - a } nq := coupNumber(firstCoupon.Number, settlement.Number, numMonths) fnArgs.Init() fnArgs.PushBack(firstCoupon) fnArgs.PushBack(maturity) fnArgs.PushBack(frequency) fnArgs.PushBack(basisArg) n = fn.COUPNUM(fnArgs) x := yld.Number/m + 1 y := dsc / e.Number p1 := x p3 := math.Pow(p1, y+nq+n.Number) term1 := redemption.Number / p3 term2 := 100 * rate.Number / m * dcnl / math.Pow(p1, nq+y) f := func(acc []float64, index float64) []float64 { return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index+nq+y)} } term3 := aggrBetween(1, math.Floor(n.Number), []float64{0}, f) term4 := 100 * rate.Number / m * anl return newNumberFormulaArg(term1 + term2 + term3[0] - term4) } // getODDFPRICE is a part of implementation of the formula function ODDFPRICE. func getODDFPRICE(f func(yld float64) float64, x, cnt, prec float64) float64 { const maxCnt = 20.0 d := func(f func(yld float64) float64, x float64) float64 { return (f(x+prec) - f(x-prec)) / (2 * prec) } fx, Fx := f(x), d(f, x) newX := x - (fx / Fx) if math.Abs(newX-x) < prec { return newX } else if cnt > maxCnt { return newX } return getODDFPRICE(f, newX, cnt+1, prec) } // ODDFYIELD function calculates the yield of a security with an odd (short or // long) first period. The syntax of the function is: // // ODDFYIELD(settlement,maturity,issue,first_coupon,rate,pr,redemption,frequency,[basis]) func (fn *formulaFuncs) ODDFYIELD(argsList *list.List) formulaArg { if argsList.Len() != 8 && argsList.Len() != 9 { return newErrorFormulaArg(formulaErrorVALUE, "ODDFYIELD requires 8 or 9 arguments") } args := fn.prepareOddfArgs("ODDFYIELD", argsList) if args.Type != ArgList { return args } settlement, maturity, issue, firstCoupon, rate, pr, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8] if basisArg.Number < 0 || basisArg.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, "invalid basis") } settlementTime := timeFromExcelTime(settlement.Number, false) maturityTime := timeFromExcelTime(maturity.Number, false) years := coupdays(settlementTime, maturityTime, int(basisArg.Number)) px := pr.Number - 100 num := rate.Number*years*100 - px denum := px/4 + years*px/2 + years*100 guess := num / denum f := func(yld float64) float64 { fnArgs := list.New().Init() fnArgs.PushBack(settlement) fnArgs.PushBack(maturity) fnArgs.PushBack(issue) fnArgs.PushBack(firstCoupon) fnArgs.PushBack(rate) fnArgs.PushBack(newNumberFormulaArg(yld)) fnArgs.PushBack(redemption) fnArgs.PushBack(frequency) fnArgs.PushBack(basisArg) return pr.Number - fn.ODDFPRICE(fnArgs).Number } if result := getODDFPRICE(f, guess, 0, 1e-7); !math.IsInf(result, 0) { return newNumberFormulaArg(result) } return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } // prepareOddlArgs checking and prepare arguments for the formula // functions ODDLPRICE and ODDLYIELD. func (fn *formulaFuncs) prepareOddlArgs(name string, argsList *list.List) formulaArg { dateValues := fn.prepareDataValueArgs(3, argsList) if dateValues.Type != ArgList { return dateValues } settlement, maturity, lastInterest := dateValues.List[0], dateValues.List[1], dateValues.List[2] if lastInterest.Number >= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > last_interest", name)) } if settlement.Number >= maturity.Number { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) } rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } if rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name)) } yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Value.(formulaArg)) if yldOrPr.Type != ArgNumber { return yldOrPr } redemption := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if redemption.Type != ArgNumber { return redemption } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name)) } frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if frequency.Type != ArgNumber { return frequency } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } basis := newNumberFormulaArg(0) if argsList.Len() == 8 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return newListFormulaArg([]formulaArg{settlement, maturity, lastInterest, rate, yldOrPr, redemption, frequency, basis}) } // oddl is an implementation of the formula functions ODDLPRICE and ODDLYIELD. func (fn *formulaFuncs) oddl(name string, argsList *list.List) formulaArg { if argsList.Len() != 7 && argsList.Len() != 8 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 7 or 8 arguments", name)) } args := fn.prepareOddlArgs(name, argsList) if args.Type != ArgList { return args } settlement, maturity, lastInterest, rate, prOrYld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7] if basisArg.Number < 0 || basisArg.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, "invalid basis") } settlementTime := timeFromExcelTime(settlement.Number, false) maturityTime := timeFromExcelTime(maturity.Number, false) basis := int(basisArg.Number) numMonths := 12 / frequency.Number fnArgs := list.New().Init() fnArgs.PushBack(lastInterest) fnArgs.PushBack(maturity) fnArgs.PushBack(frequency) fnArgs.PushBack(basisArg) nc := fn.COUPNUM(fnArgs) earlyCoupon := lastInterest.Number aggrFunc := func(acc []float64, index float64) []float64 { earlyCouponTime := timeFromExcelTime(earlyCoupon, false) lateCouponTime := changeMonth(earlyCouponTime, numMonths, false) lateCoupon, _ := timeToExcelTime(lateCouponTime, false) nl := coupdays(earlyCouponTime, lateCouponTime, basis) dci := coupdays(earlyCouponTime, maturityTime, basis) if index < nc.Number { dci = nl } var a float64 if lateCoupon < settlement.Number { a = dci } else if earlyCoupon < settlement.Number { a = coupdays(earlyCouponTime, settlementTime, basis) } startDate := earlyCoupon if settlement.Number > earlyCoupon { startDate = settlement.Number } endDate := lateCoupon if maturity.Number < lateCoupon { endDate = maturity.Number } startDateTime := timeFromExcelTime(startDate, false) endDateTime := timeFromExcelTime(endDate, false) dsc := coupdays(startDateTime, endDateTime, basis) earlyCoupon = lateCoupon dcnl := acc[0] anl := acc[1] dscnl := acc[2] return []float64{dcnl + dci/nl, anl + a/nl, dscnl + dsc/nl} } ag := aggrBetween(1, math.Floor(nc.Number), []float64{0, 0, 0}, aggrFunc) dcnl, anl, dscnl := ag[0], ag[1], ag[2] x := 100.0 * rate.Number / frequency.Number term1 := dcnl*x + redemption.Number if name == "ODDLPRICE" { term2 := dscnl*prOrYld.Number/frequency.Number + 1 term3 := anl * x return newNumberFormulaArg(term1/term2 - term3) } term2 := anl*x + prOrYld.Number term3 := frequency.Number / dscnl return newNumberFormulaArg((term1 - term2) / term2 * term3) } // ODDLPRICE function calculates the price per $100 face value of a security // with an odd (short or long) last period. The syntax of the function is: // // ODDLPRICE(settlement,maturity,last_interest,rate,yld,redemption,frequency,[basis]) func (fn *formulaFuncs) ODDLPRICE(argsList *list.List) formulaArg { return fn.oddl("ODDLPRICE", argsList) } // ODDLYIELD function calculates the yield of a security with an odd (short or // long) last period. The syntax of the function is: // // ODDLYIELD(settlement,maturity,last_interest,rate,pr,redemption,frequency,[basis]) func (fn *formulaFuncs) ODDLYIELD(argsList *list.List) formulaArg { return fn.oddl("ODDLYIELD", argsList) } // PDURATION function calculates the number of periods required for an // investment to reach a specified future value. The syntax of the function // is: // // PDURATION(rate,pv,fv) func (fn *formulaFuncs) PDURATION(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "PDURATION requires 3 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } pv := argsList.Front().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } fv := argsList.Back().Value.(formulaArg).ToNumber() if fv.Type != ArgNumber { return fv } if rate.Number <= 0 || pv.Number <= 0 || fv.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg((math.Log(fv.Number) - math.Log(pv.Number)) / math.Log(1+rate.Number)) } // PMT function calculates the constant periodic payment required to pay off // (or partially pay off) a loan or investment, with a constant interest // rate, over a specified period. The syntax of the function is: // // PMT(rate,nper,pv,[fv],[type]) func (fn *formulaFuncs) PMT(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "PMT requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "PMT allows at most 5 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } nper := argsList.Front().Next().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) if argsList.Len() >= 4 { if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { return fv } } if argsList.Len() == 5 { if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { return typ } } if typ.Number != 0 && typ.Number != 1 { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } if rate.Number != 0 { p := (-fv.Number - pv.Number*math.Pow(1+rate.Number, nper.Number)) / (1 + rate.Number*typ.Number) / ((math.Pow(1+rate.Number, nper.Number) - 1) / rate.Number) return newNumberFormulaArg(p) } return newNumberFormulaArg((-pv.Number - fv.Number) / nper.Number) } // PPMT function calculates the payment on the principal, during a specific // period of a loan or investment that is paid in constant periodic payments, // with a constant interest rate. The syntax of the function is: // // PPMT(rate,per,nper,pv,[fv],[type]) func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg { return fn.ipmt("PPMT", argsList) } // price is an implementation of the formula function PRICE. func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequency, basis formulaArg) formulaArg { if basis.Number < 0 || basis.Number > 4 { return newErrorFormulaArg(formulaErrorNUM, "invalid basis") } argsList := list.New().Init() argsList.PushBack(settlement) argsList.PushBack(maturity) argsList.PushBack(frequency) argsList.PushBack(basis) e := fn.COUPDAYS(argsList) dsc := fn.COUPDAYSNC(argsList).Number / e.Number n := fn.COUPNUM(argsList) a := fn.COUPDAYBS(argsList) ret := 0.0 if n.Number > 1 { ret = redemption.Number / math.Pow(1+yld.Number/frequency.Number, n.Number-1+dsc) ret -= 100 * rate.Number / frequency.Number * a.Number / e.Number t1 := 100 * rate.Number / frequency.Number t2 := 1 + yld.Number/frequency.Number for k := 0.0; k < n.Number; k++ { ret += t1 / math.Pow(t2, k+dsc) } } else { dsc = e.Number - a.Number t1 := 100*(rate.Number/frequency.Number) + redemption.Number t2 := (yld.Number/frequency.Number)*(dsc/e.Number) + 1 t3 := 100 * (rate.Number / frequency.Number) * (a.Number / e.Number) ret = t1/t2 - t3 } return newNumberFormulaArg(ret) } // checkPriceYieldArgs checking and prepare arguments for the formula functions // PRICE and YIELD. func checkPriceYieldArgs(name string, rate, prYld, redemption, frequency formulaArg) formulaArg { if rate.Type != ArgNumber { return rate } if rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name)) } if prYld.Type != ArgNumber { return prYld } if redemption.Type != ArgNumber { return redemption } if name == "PRICE" { if prYld.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0") } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0") } } if name == "YIELD" { if prYld.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELD requires pr > 0") } if redemption.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELD requires redemption >= 0") } } if frequency.Type != ArgNumber { return frequency } if !validateFrequency(frequency.Number) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newEmptyFormulaArg() } // priceYield is an implementation of the formula functions PRICE and YIELD. func (fn *formulaFuncs) priceYield(name string, argsList *list.List) formulaArg { if argsList.Len() != 6 && argsList.Len() != 7 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 or 7 arguments", name)) } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() prYld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if arg := checkPriceYieldArgs(name, rate, prYld, redemption, frequency); arg.Type != ArgEmpty { return arg } basis := newNumberFormulaArg(0) if argsList.Len() == 7 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } if name == "PRICE" { return fn.price(settlement, maturity, rate, prYld, redemption, frequency, basis) } return fn.yield(settlement, maturity, rate, prYld, redemption, frequency, basis) } // PRICE function calculates the price, per $100 face value of a security that // pays periodic interest. The syntax of the function is: // // PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis]) func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg { return fn.priceYield("PRICE", argsList) } // PRICEDISC function calculates the price, per $100 face value of a // discounted security. The syntax of the function is: // // PRICEDISC(settlement,maturity,discount,redemption,[basis]) func (fn *formulaFuncs) PRICEDISC(argsList *list.List) formulaArg { if argsList.Len() != 4 && argsList.Len() != 5 { return newErrorFormulaArg(formulaErrorVALUE, "PRICEDISC requires 4 or 5 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] if maturity.Number <= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires maturity > settlement") } discount := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if discount.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if discount.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires discount > 0") } redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if redemption.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires redemption > 0") } basis := newNumberFormulaArg(0) if argsList.Len() == 5 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } return newNumberFormulaArg(redemption.Number * (1 - discount.Number*frac.Number)) } // PRICEMAT function calculates the price, per $100 face value of a security // that pays interest at maturity. The syntax of the function is: // // PRICEMAT(settlement,maturity,issue,rate,yld,[basis]) func (fn *formulaFuncs) PRICEMAT(argsList *list.List) formulaArg { if argsList.Len() != 5 && argsList.Len() != 6 { return newErrorFormulaArg(formulaErrorVALUE, "PRICEMAT requires 5 or 6 arguments") } args := fn.prepareDataValueArgs(3, argsList) if args.Type != ArgList { return args } settlement, maturity, issue := args.List[0], args.List[1], args.List[2] if settlement.Number >= maturity.Number { return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires maturity > settlement") } if issue.Number >= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires settlement > issue") } rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } if rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires rate >= 0") } yld := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if yld.Type != ArgNumber { return yld } if yld.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires yld >= 0") } basis := newNumberFormulaArg(0) if argsList.Len() == 6 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if dsm.Type != ArgNumber { return dsm } dis := yearFrac(issue.Number, settlement.Number, int(basis.Number)) dim := yearFrac(issue.Number, maturity.Number, int(basis.Number)) return newNumberFormulaArg(((1+dim.Number*rate.Number)/(1+dsm.Number*yld.Number) - dis.Number*rate.Number) * 100) } // PV function calculates the Present Value of an investment, based on a // series of future payments. The syntax of the function is: // // PV(rate,nper,pmt,[fv],[type]) func (fn *formulaFuncs) PV(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "PV requires at least 3 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "PV allows at most 5 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } nper := argsList.Front().Next().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pmt.Type != ArgNumber { return pmt } fv := newNumberFormulaArg(0) if argsList.Len() >= 4 { if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { return fv } } t := newNumberFormulaArg(0) if argsList.Len() == 5 { if t = argsList.Back().Value.(formulaArg).ToNumber(); t.Type != ArgNumber { return t } if t.Number != 0 { t.Number = 1 } } if rate.Number == 0 { return newNumberFormulaArg(-pmt.Number*nper.Number - fv.Number) } return newNumberFormulaArg((((1-math.Pow(1+rate.Number, nper.Number))/rate.Number)*pmt.Number*(1+rate.Number*t.Number) - fv.Number) / math.Pow(1+rate.Number, nper.Number)) } // rate is an implementation of the formula function RATE. func (fn *formulaFuncs) rate(nper, pmt, pv, fv, t, guess formulaArg) formulaArg { maxIter, iter, isClose, epsMax, rate := 100, 0, false, 1e-6, guess.Number for iter < maxIter && !isClose { t1 := math.Pow(rate+1, nper.Number) t2 := math.Pow(rate+1, nper.Number-1) rt := rate*t.Number + 1 p0 := pmt.Number * (t1 - 1) f1 := fv.Number + t1*pv.Number + p0*rt/rate n1 := nper.Number * t2 * pv.Number n2 := p0 * rt / math.Pow(rate, 2) f2 := math.Nextafter(n1, n1) - math.Nextafter(n2, n2) f3 := (nper.Number*pmt.Number*t2*rt + p0*t.Number) / rate delta := f1 / (f2 + f3) if math.Abs(delta) < epsMax { isClose = true } iter++ rate -= delta } return newNumberFormulaArg(rate) } // RATE function calculates the interest rate required to pay off a specified // amount of a loan, or to reach a target amount on an investment, over a // given period. The syntax of the function is: // // RATE(nper,pmt,pv,[fv],[type],[guess]) func (fn *formulaFuncs) RATE(argsList *list.List) formulaArg { if argsList.Len() < 3 { return newErrorFormulaArg(formulaErrorVALUE, "RATE requires at least 3 arguments") } if argsList.Len() > 6 { return newErrorFormulaArg(formulaErrorVALUE, "RATE allows at most 6 arguments") } nper := argsList.Front().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber { return nper } pmt := argsList.Front().Next().Value.(formulaArg).ToNumber() if pmt.Type != ArgNumber { return pmt } pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pv.Type != ArgNumber { return pv } fv := newNumberFormulaArg(0) if argsList.Len() >= 4 { if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { return fv } } t := newNumberFormulaArg(0) if argsList.Len() >= 5 { if t = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); t.Type != ArgNumber { return t } if t.Number != 0 { t.Number = 1 } } guess := newNumberFormulaArg(0.1) if argsList.Len() == 6 { if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { return guess } } return fn.rate(nper, pmt, pv, fv, t, guess) } // RECEIVED function calculates the amount received at maturity for a fully // invested security. The syntax of the function is: // // RECEIVED(settlement,maturity,investment,discount,[basis]) func (fn *formulaFuncs) RECEIVED(argsList *list.List) formulaArg { if argsList.Len() < 4 { return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED requires at least 4 arguments") } if argsList.Len() > 5 { return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED allows at most 5 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if investment.Type != ArgNumber { return investment } discount := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if discount.Type != ArgNumber { return discount } if discount.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "RECEIVED requires discount > 0") } basis := newNumberFormulaArg(0) if argsList.Len() == 5 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } return newNumberFormulaArg(investment.Number / (1 - discount.Number*frac.Number)) } // RRI function calculates the equivalent interest rate for an investment with // specified present value, future value and duration. The syntax of the // function is: // // RRI(nper,pv,fv) func (fn *formulaFuncs) RRI(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "RRI requires 3 arguments") } nper := argsList.Front().Value.(formulaArg).ToNumber() pv := argsList.Front().Next().Value.(formulaArg).ToNumber() fv := argsList.Back().Value.(formulaArg).ToNumber() if nper.Type != ArgNumber || pv.Type != ArgNumber || fv.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if nper.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "RRI requires nper argument to be > 0") } if pv.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "RRI requires pv argument to be > 0") } if fv.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "RRI requires fv argument to be >= 0") } return newNumberFormulaArg(math.Pow(fv.Number/pv.Number, 1/nper.Number) - 1) } // SLN function calculates the straight line depreciation of an asset for one // period. The syntax of the function is: // // SLN(cost,salvage,life) func (fn *formulaFuncs) SLN(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "SLN requires 3 arguments") } cost := argsList.Front().Value.(formulaArg).ToNumber() salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() life := argsList.Back().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if life.Number == 0 { return newErrorFormulaArg(formulaErrorNUM, "SLN requires life argument to be > 0") } return newNumberFormulaArg((cost.Number - salvage.Number) / life.Number) } // SYD function calculates the sum-of-years' digits depreciation for a // specified period in the lifetime of an asset. The syntax of the function // is: // // SYD(cost,salvage,life,per) func (fn *formulaFuncs) SYD(argsList *list.List) formulaArg { if argsList.Len() != 4 { return newErrorFormulaArg(formulaErrorVALUE, "SYD requires 4 arguments") } cost := argsList.Front().Value.(formulaArg).ToNumber() salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() life := argsList.Back().Prev().Value.(formulaArg).ToNumber() per := argsList.Back().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber || per.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if life.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "SYD requires life argument to be > 0") } if per.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "SYD requires per argument to be > 0") } if per.Number > life.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(((cost.Number - salvage.Number) * (life.Number - per.Number + 1) * 2) / (life.Number * (life.Number + 1))) } // TBILLEQ function calculates the bond-equivalent yield for a Treasury Bill. // The syntax of the function is: // // TBILLEQ(settlement,maturity,discount) func (fn *formulaFuncs) TBILLEQ(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "TBILLEQ requires 3 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] dsm := maturity.Number - settlement.Number if dsm > 365 || maturity.Number <= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } discount := argsList.Back().Value.(formulaArg).ToNumber() if discount.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if discount.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg((365 * discount.Number) / (360 - discount.Number*dsm)) } // TBILLPRICE function returns the price, per $100 face value, of a Treasury // Bill. The syntax of the function is: // // TBILLPRICE(settlement,maturity,discount) func (fn *formulaFuncs) TBILLPRICE(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "TBILLPRICE requires 3 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] dsm := maturity.Number - settlement.Number if dsm > 365 || maturity.Number <= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } discount := argsList.Back().Value.(formulaArg).ToNumber() if discount.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if discount.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(100 * (1 - discount.Number*dsm/360)) } // TBILLYIELD function calculates the yield of a Treasury Bill. The syntax of // the function is: // // TBILLYIELD(settlement,maturity,pr) func (fn *formulaFuncs) TBILLYIELD(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "TBILLYIELD requires 3 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] dsm := maturity.Number - settlement.Number if dsm > 365 || maturity.Number <= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } pr := argsList.Back().Value.(formulaArg).ToNumber() if pr.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if pr.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(((100 - pr.Number) / pr.Number) * (360 / dsm)) } // prepareVdbArgs checking and prepare arguments for the formula function // VDB. func (fn *formulaFuncs) prepareVdbArgs(argsList *list.List) formulaArg { cost := argsList.Front().Value.(formulaArg).ToNumber() if cost.Type != ArgNumber { return cost } if cost.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "VDB requires cost >= 0") } salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() if salvage.Type != ArgNumber { return salvage } if salvage.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "VDB requires salvage >= 0") } life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if life.Type != ArgNumber { return life } if life.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "VDB requires life > 0") } startPeriod := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if startPeriod.Type != ArgNumber { return startPeriod } if startPeriod.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period > 0") } endPeriod := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if endPeriod.Type != ArgNumber { return endPeriod } if startPeriod.Number > endPeriod.Number { return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period <= end_period") } if endPeriod.Number > life.Number { return newErrorFormulaArg(formulaErrorNUM, "VDB requires end_period <= life") } factor := newNumberFormulaArg(2) if argsList.Len() > 5 { if factor = argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if factor.Number < 0 { return newErrorFormulaArg(formulaErrorVALUE, "VDB requires factor >= 0") } } return newListFormulaArg([]formulaArg{cost, salvage, life, startPeriod, endPeriod, factor}) } // vdb is a part of implementation of the formula function VDB. func (fn *formulaFuncs) vdb(cost, salvage, life, life1, period, factor formulaArg) formulaArg { var ddb, vdb, sln, term float64 endInt, cs, nowSln := math.Ceil(period.Number), cost.Number-salvage.Number, false ddbArgs := list.New() for i := 1.0; i <= endInt; i++ { if !nowSln { ddbArgs.Init() ddbArgs.PushBack(cost) ddbArgs.PushBack(salvage) ddbArgs.PushBack(life) ddbArgs.PushBack(newNumberFormulaArg(i)) ddbArgs.PushBack(factor) ddb = fn.DDB(ddbArgs).Number sln = cs / (life1.Number - i + 1) if sln > ddb && i != endInt { term = sln nowSln = true } else { term = ddb cs -= ddb } } else { term = sln } if i == endInt { term *= period.Number + 1 - endInt } vdb += term } return newNumberFormulaArg(vdb) } // VDB function calculates the depreciation of an asset, using the Double // Declining Balance Method, or another specified depreciation rate, for a // specified period (including partial periods). The syntax of the function // is: // // VDB(cost,salvage,life,start_period,end_period,[factor],[no_switch]) func (fn *formulaFuncs) VDB(argsList *list.List) formulaArg { if argsList.Len() < 5 || argsList.Len() > 7 { return newErrorFormulaArg(formulaErrorVALUE, "VDB requires 5 or 7 arguments") } args := fn.prepareVdbArgs(argsList) if args.Type != ArgList { return args } cost, salvage, life, startPeriod, endPeriod, factor := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] noSwitch := newBoolFormulaArg(false) if argsList.Len() > 6 { if noSwitch = argsList.Back().Value.(formulaArg).ToBool(); noSwitch.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } startInt, endInt, vdb, ddbArgs := math.Floor(startPeriod.Number), math.Ceil(endPeriod.Number), newNumberFormulaArg(0), list.New() if noSwitch.Number == 1 { for i := startInt + 1; i <= endInt; i++ { ddbArgs.Init() ddbArgs.PushBack(cost) ddbArgs.PushBack(salvage) ddbArgs.PushBack(life) ddbArgs.PushBack(newNumberFormulaArg(i)) ddbArgs.PushBack(factor) term := fn.DDB(ddbArgs) switch i { case startInt + 1: term.Number *= math.Min(endPeriod.Number, startInt+1) - startPeriod.Number case endInt: term.Number *= endPeriod.Number + 1 - endInt } vdb.Number += term.Number } return vdb } cost.Number -= fn.vdb(cost, salvage, life, life, startPeriod, factor).Number return fn.vdb(cost, salvage, life, newNumberFormulaArg(life.Number-startPeriod.Number), newNumberFormulaArg(endPeriod.Number-startPeriod.Number), factor) } // prepareXArgs prepare arguments for the formula function XIRR and XNPV. func (fn *formulaFuncs) prepareXArgs(values, dates formulaArg) (valuesArg, datesArg []float64, err formulaArg) { for _, arg := range values.ToList() { if numArg := arg.ToNumber(); numArg.Type == ArgNumber { valuesArg = append(valuesArg, numArg.Number) continue } err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return } if len(valuesArg) < 2 { err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) return } date := 0.0 for _, arg := range dates.ToList() { if arg.Type == ArgNumber { datesArg = append(datesArg, arg.Number) if arg.Number < date { err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return } date = arg.Number continue } err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return } if len(valuesArg) != len(datesArg) { err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) return } err = newEmptyFormulaArg() return } // xirr is an implementation of the formula function XIRR. func (fn *formulaFuncs) xirr(values, dates []float64, guess float64) formulaArg { positive, negative := false, false for i := 0; i < len(values); i++ { if values[i] > 0 { positive = true } if values[i] < 0 { negative = true } } if !positive || !negative { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } result, epsMax, count, maxIterate, err := guess, 1e-10, 0, 50, false for { resultValue := xirrPart1(values, dates, result) newRate := result - resultValue/xirrPart2(values, dates, result) epsRate := math.Abs(newRate - result) result = newRate count++ if epsRate <= epsMax || math.Abs(resultValue) <= epsMax { break } if count > maxIterate { err = true break } } if err || math.IsNaN(result) || math.IsInf(result, 0) { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } return newNumberFormulaArg(result) } // xirrPart1 is a part of implementation of the formula function XIRR. func xirrPart1(values, dates []float64, rate float64) float64 { r := rate + 1 result := values[0] vlen := len(values) firstDate := dates[0] for i := 1; i < vlen; i++ { result += values[i] / math.Pow(r, (dates[i]-firstDate)/365) } return result } // xirrPart2 is a part of implementation of the formula function XIRR. func xirrPart2(values, dates []float64, rate float64) float64 { r := rate + 1 result := 0.0 vlen := len(values) firstDate := dates[0] for i := 1; i < vlen; i++ { frac := (dates[i] - firstDate) / 365 result -= frac * values[i] / math.Pow(r, frac+1) } return result } // XIRR function returns the Internal Rate of Return for a supplied series of // cash flows (i.e. a set of values, which includes an initial investment // value and a series of net income values) occurring at a series of supplied // dates. The syntax of the function is: // // XIRR(values,dates,[guess]) func (fn *formulaFuncs) XIRR(argsList *list.List) formulaArg { if argsList.Len() != 2 && argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires 2 or 3 arguments") } values, dates, err := fn.prepareXArgs(argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)) if err.Type != ArgEmpty { return err } guess := newNumberFormulaArg(0) if argsList.Len() == 3 { if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } if guess.Number <= -1 { return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires guess > -1") } } return fn.xirr(values, dates, guess.Number) } // XNPV function calculates the Net Present Value for a schedule of cash flows // that is not necessarily periodic. The syntax of the function is: // // XNPV(rate,values,dates) func (fn *formulaFuncs) XNPV(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires 3 arguments") } rate := argsList.Front().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } if rate.Number <= 0 { return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires rate > 0") } values, dates, err := fn.prepareXArgs(argsList.Front().Next().Value.(formulaArg), argsList.Back().Value.(formulaArg)) if err.Type != ArgEmpty { return err } date1, xnpv := dates[0], 0.0 for idx, value := range values { xnpv += value / math.Pow(1+rate.Number, (dates[idx]-date1)/365) } return newNumberFormulaArg(xnpv) } // yield is an implementation of the formula function YIELD. func (fn *formulaFuncs) yield(settlement, maturity, rate, pr, redemption, frequency, basis formulaArg) formulaArg { priceN, yield1, yield2 := newNumberFormulaArg(0), newNumberFormulaArg(0), newNumberFormulaArg(1) price1 := fn.price(settlement, maturity, rate, yield1, redemption, frequency, basis) if price1.Type != ArgNumber { return price1 } price2 := fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis) yieldN := newNumberFormulaArg((yield2.Number - yield1.Number) * 0.5) for iter := 0; iter < 100 && priceN.Number != pr.Number; iter++ { priceN = fn.price(settlement, maturity, rate, yieldN, redemption, frequency, basis) if pr.Number == price1.Number { return yield1 } else if pr.Number == price2.Number { return yield2 } else if pr.Number == priceN.Number { return yieldN } else if pr.Number < price2.Number { yield2.Number *= 2.0 price2 = fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis) yieldN.Number = (yield2.Number - yield1.Number) * 0.5 } else { if pr.Number < priceN.Number { yield1 = yieldN price1 = priceN } else { yield2 = yieldN price2 = priceN } f1 := (yield2.Number - yield1.Number) * ((pr.Number - price2.Number) / (price1.Number - price2.Number)) yieldN.Number = yield2.Number - math.Nextafter(f1, f1) } } return yieldN } // YIELD function calculates the Yield of a security that pays periodic // interest. The syntax of the function is: // // YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis]) func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg { return fn.priceYield("YIELD", argsList) } // YIELDDISC function calculates the annual yield of a discounted security. // The syntax of the function is: // // YIELDDISC(settlement,maturity,pr,redemption,[basis]) func (fn *formulaFuncs) YIELDDISC(argsList *list.List) formulaArg { if argsList.Len() != 4 && argsList.Len() != 5 { return newErrorFormulaArg(formulaErrorVALUE, "YIELDDISC requires 4 or 5 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if pr.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if pr.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires pr > 0") } redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if redemption.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } if redemption.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires redemption > 0") } basis := newNumberFormulaArg(0) if argsList.Len() == 5 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) if frac.Type != ArgNumber { return frac } return newNumberFormulaArg((redemption.Number/pr.Number - 1) / frac.Number) } // YIELDMAT function calculates the annual yield of a security that pays // interest at maturity. The syntax of the function is: // // YIELDMAT(settlement,maturity,issue,rate,pr,[basis]) func (fn *formulaFuncs) YIELDMAT(argsList *list.List) formulaArg { if argsList.Len() != 5 && argsList.Len() != 6 { return newErrorFormulaArg(formulaErrorVALUE, "YIELDMAT requires 5 or 6 arguments") } args := fn.prepareDataValueArgs(2, argsList) if args.Type != ArgList { return args } settlement, maturity := args.List[0], args.List[1] arg := list.New().Init() issue := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() if issue.Type != ArgNumber { arg.PushBack(argsList.Front().Next().Next().Value.(formulaArg)) issue = fn.DATEVALUE(arg) if issue.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } } if issue.Number >= settlement.Number { return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires settlement > issue") } rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() if rate.Type != ArgNumber { return rate } if rate.Number < 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires rate >= 0") } pr := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() if pr.Type != ArgNumber { return pr } if pr.Number <= 0 { return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires pr > 0") } basis := newNumberFormulaArg(0) if argsList.Len() == 6 { if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } dim := yearFrac(issue.Number, maturity.Number, int(basis.Number)) if dim.Type != ArgNumber { return dim } dis := yearFrac(issue.Number, settlement.Number, int(basis.Number)) dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) f1 := dim.Number * rate.Number result := 1 + math.Nextafter(f1, f1) result /= pr.Number/100 + dis.Number*rate.Number result-- result /= dsm.Number return newNumberFormulaArg(result) } // Database Functions // calcDatabase defines the structure for formula database. type calcDatabase struct { col, row int indexMap map[int]int database [][]formulaArg criteria [][]formulaArg } // newCalcDatabase function returns formula database by given data range of // cells containing the database, field and criteria range. func newCalcDatabase(database, field, criteria formulaArg) *calcDatabase { db := calcDatabase{ indexMap: make(map[int]int), database: database.Matrix, criteria: criteria.Matrix, } exp := len(database.Matrix) < 2 || len(database.Matrix[0]) < 1 || len(criteria.Matrix) < 2 || len(criteria.Matrix[0]) < 1 if field.Type != ArgEmpty { if db.col = db.columnIndex(database.Matrix, field); exp || db.col < 0 || len(db.database[0]) <= db.col { return nil } return &db } if db.col = -1; exp { return nil } return &db } // columnIndex return index by specifies column field within the database for // which user want to return the count of non-blank cells. func (db *calcDatabase) columnIndex(database [][]formulaArg, field formulaArg) int { num := field.ToNumber() if num.Type != ArgNumber && len(database) > 0 { for i := 0; i < len(database[0]); i++ { if title := database[0][i]; strings.EqualFold(title.Value(), field.Value()) { return i } } return -1 } return int(num.Number - 1) } // criteriaEval evaluate formula criteria expression. func (db *calcDatabase) criteriaEval() bool { var ( columns, rows = len(db.criteria[0]), len(db.criteria) criteria = db.criteria k int matched bool ) if len(db.indexMap) == 0 { fields := criteria[0] for j := 0; j < columns; j++ { if k = db.columnIndex(db.database, fields[j]); k < 0 { return false } db.indexMap[j] = k } } for i := 1; !matched && i < rows; i++ { matched = true for j := 0; matched && j < columns; j++ { criteriaExp := db.criteria[i][j] if criteriaExp.Value() == "" { continue } criteria := formulaCriteriaParser(criteriaExp) cell := db.database[db.row][db.indexMap[j]] matched, _ = formulaCriteriaEval(cell, criteria) } } return matched } // value returns the current cell value. func (db *calcDatabase) value() formulaArg { if db.col == -1 { return db.database[db.row][len(db.database[db.row])-1] } return db.database[db.row][db.col] } // next will return true if find the matched cell in the database. func (db *calcDatabase) next() bool { matched, rows := false, len(db.database) for !matched && db.row < rows { if db.row++; db.row < rows { matched = db.criteriaEval() } } return matched } // database is an implementation of the formula functions DAVERAGE, DMAX and DMIN. func (fn *formulaFuncs) database(name string, argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name)) } database := argsList.Front().Value.(formulaArg) field := argsList.Front().Next().Value.(formulaArg) criteria := argsList.Back().Value.(formulaArg) db := newCalcDatabase(database, field, criteria) if db == nil { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } args := list.New() for db.next() { args.PushBack(db.value()) } switch name { case "DMAX": return fn.MAX(args) case "DMIN": return fn.MIN(args) case "DPRODUCT": return fn.PRODUCT(args) case "DSTDEV": return fn.STDEV(args) case "DSTDEVP": return fn.STDEVP(args) case "DSUM": return fn.SUM(args) case "DVAR": return fn.VAR(args) case "DVARP": return fn.VARP(args) default: return fn.AVERAGE(args) } } // DAVERAGE function calculates the average (statistical mean) of values in a // field (column) in a database for selected records, that satisfy // user-specified criteria. The syntax of the function is: // // DAVERAGE(database,field,criteria) func (fn *formulaFuncs) DAVERAGE(argsList *list.List) formulaArg { return fn.database("DAVERAGE", argsList) } // dcount is an implementation of the formula functions DCOUNT and DCOUNTA. func (fn *formulaFuncs) dcount(name string, argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) } if argsList.Len() > 3 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name)) } field := newEmptyFormulaArg() criteria := argsList.Back().Value.(formulaArg) if argsList.Len() > 2 { field = argsList.Front().Next().Value.(formulaArg) } database := argsList.Front().Value.(formulaArg) db := newCalcDatabase(database, field, criteria) if db == nil { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } args := list.New() for db.next() { args.PushBack(db.value()) } if name == "DCOUNT" { return fn.COUNT(args) } return fn.COUNTA(args) } // DCOUNT function returns the number of cells containing numeric values, in a // field (column) of a database for selected records only. The records to be // included in the count are those that satisfy a set of one or more // user-specified criteria. The syntax of the function is: // // DCOUNT(database,[field],criteria) func (fn *formulaFuncs) DCOUNT(argsList *list.List) formulaArg { return fn.dcount("DCOUNT", argsList) } // DCOUNTA function returns the number of non-blank cells, in a field // (column) of a database for selected records only. The records to be // included in the count are those that satisfy a set of one or more // user-specified criteria. The syntax of the function is: // // DCOUNTA(database,[field],criteria) func (fn *formulaFuncs) DCOUNTA(argsList *list.List) formulaArg { return fn.dcount("DCOUNTA", argsList) } // DGET function returns a single value from a column of a database. The record // is selected via a set of one or more user-specified criteria. The syntax of // the function is: // // DGET(database,field,criteria) func (fn *formulaFuncs) DGET(argsList *list.List) formulaArg { if argsList.Len() != 3 { return newErrorFormulaArg(formulaErrorVALUE, "DGET requires 3 arguments") } database := argsList.Front().Value.(formulaArg) field := argsList.Front().Next().Value.(formulaArg) criteria := argsList.Back().Value.(formulaArg) db := newCalcDatabase(database, field, criteria) if db == nil { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } value := newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) if db.next() { if value = db.value(); db.next() { return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) } } return value } // DMAX function finds the maximum value in a field (column) in a database for // selected records only. The records to be included in the calculation are // defined by a set of one or more user-specified criteria. The syntax of the // function is: // // DMAX(database,field,criteria) func (fn *formulaFuncs) DMAX(argsList *list.List) formulaArg { return fn.database("DMAX", argsList) } // DMIN function finds the minimum value in a field (column) in a database for // selected records only. The records to be included in the calculation are // defined by a set of one or more user-specified criteria. The syntax of the // function is: // // DMIN(database,field,criteria) func (fn *formulaFuncs) DMIN(argsList *list.List) formulaArg { return fn.database("DMIN", argsList) } // DPRODUCT function calculates the product of a field (column) in a database // for selected records, that satisfy user-specified criteria. The syntax of // the function is: // // DPRODUCT(database,field,criteria) func (fn *formulaFuncs) DPRODUCT(argsList *list.List) formulaArg { return fn.database("DPRODUCT", argsList) } // DSTDEV function calculates the sample standard deviation of a field // (column) in a database for selected records only. The records to be // included in the calculation are defined by a set of one or more // user-specified criteria. The syntax of the function is: // // DSTDEV(database,field,criteria) func (fn *formulaFuncs) DSTDEV(argsList *list.List) formulaArg { return fn.database("DSTDEV", argsList) } // DSTDEVP function calculates the standard deviation of a field (column) in a // database for selected records only. The records to be included in the // calculation are defined by a set of one or more user-specified criteria. // The syntax of the function is: // // DSTDEVP(database,field,criteria) func (fn *formulaFuncs) DSTDEVP(argsList *list.List) formulaArg { return fn.database("DSTDEVP", argsList) } // DSUM function calculates the sum of a field (column) in a database for // selected records, that satisfy user-specified criteria. The syntax of the // function is: // // DSUM(database,field,criteria) func (fn *formulaFuncs) DSUM(argsList *list.List) formulaArg { return fn.database("DSUM", argsList) } // DVAR function calculates the sample variance of a field (column) in a // database for selected records only. The records to be included in the // calculation are defined by a set of one or more user-specified criteria. // The syntax of the function is: // // DVAR(database,field,criteria) func (fn *formulaFuncs) DVAR(argsList *list.List) formulaArg { return fn.database("DVAR", argsList) } // DVARP function calculates the variance (for an entire population), of the // values in a field (column) in a database for selected records only. The // records to be included in the calculation are defined by a set of one or // more user-specified criteria. The syntax of the function is: // // DVARP(database,field,criteria) func (fn *formulaFuncs) DVARP(argsList *list.List) formulaArg { return fn.database("DVARP", argsList) } // DISPIMG function calculates the Kingsoft WPS Office embedded image ID. The // syntax of the function is: // // DISPIMG(picture_name,display_mode) func (fn *formulaFuncs) DISPIMG(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "DISPIMG requires 2 numeric arguments") } return argsList.Front().Value.(formulaArg) } // sortbyArgs holds the parsed arguments for the SORTBY function. type sortbyArgs struct { array []formulaArg cols int rows int sortKeys []sortbyKey } // sortbyKey represents a single sort key with its associated array and order. type sortbyKey struct { byArray []formulaArg cols int rows int ascending bool // true = ascending (1 or omitted), false = descending (-1) } // rowWithKeys pairs a data row with its corresponding sort key values. type rowWithKeys struct { rowData []formulaArg sortKeys [][]formulaArg // up to 3 sets of sort keys } // SORTBY function sorts the contents of a range or array based on the values // in a corresponding range or array. The syntax of the function is: // // SORTBY(array,by_array1,[sort_order1],[by_array2,sort_order2],[by_array3, sort_order3]) func (fn *formulaFuncs) SORTBY(argsList *list.List) formulaArg { args, errArg := prepareSortbyArgs(argsList) if errArg != nil { return *errArg } rowsWithKeys := make([]rowWithKeys, args.rows) for i := 0; i < args.rows; i++ { rowsWithKeys[i].rowData = args.array[i*args.cols : (i+1)*args.cols] rowsWithKeys[i].sortKeys = make([][]formulaArg, len(args.sortKeys)) for keyIdx, sortKey := range args.sortKeys { rowsWithKeys[i].sortKeys[keyIdx] = sortKey.byArray[i*sortKey.cols : (i+1)*sortKey.cols] } } sort.Slice(rowsWithKeys, func(i, j int) bool { return compareRowsForSortby(rowsWithKeys[i], rowsWithKeys[j], args.sortKeys) }) result := make([][]formulaArg, args.rows) for i, row := range rowsWithKeys { result[i] = row.rowData } return newMatrixFormulaArg(result) } // checkSortbyArgs checking arguments for the formula function SORTBY. func checkSortbyArgs(argsList *list.List) formulaArg { argsLen := argsList.Len() if argsLen < 2 { return newErrorFormulaArg(formulaErrorVALUE, "SORTBY requires at least 2 arguments") } if argsLen > 7 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("SORTBY takes at most 7 arguments, received %d", argsLen)) } if argsLen != 2 && argsLen != 3 && argsLen != 5 && argsLen != 7 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("SORTBY requires 2, 3, 5, or 7 arguments, received %d", argsLen)) } arrArg := argsList.Front().Value.(formulaArg).ToList() if len(arrArg) == 0 { return newErrorFormulaArg(formulaErrorVALUE, "missing first argument to SORTBY") } if arrArg[0].Type == ArgError { return arrArg[0] } return newListFormulaArg(arrArg) } // prepareSortbyArgs prepare arguments for the formula function SORTBY. func prepareSortbyArgs(argsList *list.List) (sortbyArgs, *formulaArg) { res := sortbyArgs{} args := checkSortbyArgs(argsList) if args.Type != ArgList { return res, &args } res.array = args.List argsLen := argsList.Len() array := argsList.Front() tempList := list.New() tempList.PushBack(array.Value) rmin, rmax := calcColsRowsMinMax(false, tempList) cmin, cmax := calcColsRowsMinMax(true, tempList) res.cols, res.rows = cmax-cmin+1, rmax-rmin+1 byArray := array.Next() keyCount := 0 for byArray != nil && keyCount < 3 { var key sortbyKey key.byArray = byArray.Value.(formulaArg).ToList() if len(key.byArray) == 0 { errArg := newErrorFormulaArg(formulaErrorVALUE, "missing by_array argument to SORTBY") return res, &errArg } if key.byArray[0].Type == ArgError { return res, &key.byArray[0] } tempList := list.New() tempList.PushBack(byArray.Value) rmin, rmax := calcColsRowsMinMax(false, tempList) cmin, cmax := calcColsRowsMinMax(true, tempList) key.cols, key.rows = cmax-cmin+1, rmax-rmin+1 if key.rows != res.rows { errArg := newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("by_array dimensions (%d rows) do not match array dimensions (%d rows)", key.rows, res.rows)) return res, &errArg } key.ascending = true nextByArray, errArg := parseSortOrderArg(byArray.Next(), &key, keyCount, argsLen) if errArg != nil { return res, errArg } byArray = nextByArray res.sortKeys = append(res.sortKeys, key) keyCount++ } return res, nil } // parseSortOrderArg processes the optional sort_order argument for a SORTBY // key. It updates the key's ascending field and returns the next element in the // list. Returns an error if the sort_order value is invalid. func parseSortOrderArg(byArray *list.Element, key *sortbyKey, keyCount, argsLen int) (*list.Element, *formulaArg) { if byArray == nil { return nil, nil } sortOrderArg := byArray.Value.(formulaArg).ToNumber() expectedSortOrder := false if keyCount == 0 && (argsLen == 3 || argsLen == 5 || argsLen == 7) { expectedSortOrder = true } else if keyCount == 1 && (argsLen == 5 || argsLen == 7) { expectedSortOrder = true } else if keyCount == 2 && argsLen == 7 { expectedSortOrder = true } if sortOrderArg.Type == ArgError { if expectedSortOrder { return byArray, &sortOrderArg } return byArray, nil } switch sortOrderArg.Number { case -1: key.ascending = false return byArray.Next(), nil case 1: key.ascending = true return byArray.Next(), nil } errArg := newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("sort_order must be 1 or -1, received %v", sortOrderArg.Number)) return byArray, &errArg } // compareRowsForSortby compares two rows using multiple sort keys. // Returns true if row i should come before row j. func compareRowsForSortby(i, j rowWithKeys, sortKeys []sortbyKey) bool { for idx, sortKey := range sortKeys { lhs, rhs := i.sortKeys[idx], j.sortKeys[idx] for colIdx := 0; colIdx < len(lhs) && colIdx < len(rhs); colIdx++ { criteria := compareFormulaArg(lhs[colIdx], rhs[colIdx], newNumberFormulaArg(matchModeMaxLess), false) if criteria == criteriaEq { continue } if sortKey.ascending { return criteria == criteriaL } return criteria == criteriaG } } return false } ================================================ FILE: calc_test.go ================================================ package excelize import ( "container/list" "math" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/xuri/efp" ) func prepareCalcData(cellData [][]interface{}) *File { f := NewFile() for r, row := range cellData { for c, value := range row { cell, _ := CoordinatesToCellName(c+1, r+1) _ = f.SetCellValue("Sheet1", cell, value) } } return f } func TestCalcCellValue(t *testing.T) { cellData := [][]interface{}{ {1, 4, nil, "Month", "Team", "Sales"}, {2, 5, nil, "Jan", "North 1", 36693}, {3, nil, nil, "Jan", "North 2", 22100}, {0, nil, nil, "Jan", "South 1", 53321}, {nil, nil, nil, "Jan", "South 2", 34440}, {nil, nil, nil, "Feb", "North 1", 29889}, {nil, nil, nil, "Feb", "North 2", 50090}, {nil, nil, nil, "Feb", "South 1", 32080}, {nil, nil, nil, "Feb", "South 2", 45500}, } mathCalc := map[string]string{ "2^3": "8", "1=1": "TRUE", "1=2": "FALSE", "1<2": "TRUE", "3<2": "FALSE", "1<\"-1\"": "TRUE", "\"-1\"<1": "FALSE", "\"-1\"<\"-2\"": "TRUE", "2<=3": "TRUE", "2<=1": "FALSE", "1<=\"-1\"": "TRUE", "\"-1\"<=1": "FALSE", "\"-1\"<=\"-2\"": "TRUE", "2>1": "TRUE", "2>3": "FALSE", "1>\"-1\"": "FALSE", "\"-1\">-1": "TRUE", "\"-1\">\"-2\"": "FALSE", "2>=1": "TRUE", "2>=3": "FALSE", "1>=\"-1\"": "FALSE", "\"-1\">=-1": "TRUE", "\"-1\">=\"-2\"": "FALSE", "-----1+1": "0", "------1+1": "2", "---1---1": "-2", "---1----1": "0", "1&2": "12", "15%": "0.15", "1+20%": "1.2", "{1}+2": "3", "1+{2}": "3", "{1}+{2}": "3", "A1+(B1-C1)": "5", "A1+(C1-B1)": "-3", "A1&B1&C1": "14", "B1+C1": "4", "C1+B1": "4", "C1+C1": "0", "\"A\"=\"A\"": "TRUE", "\"A\"<>\"A\"": "FALSE", "TRUE()&FALSE()": "TRUEFALSE", "TRUE()&FALSE()<>FALSE": "TRUE", "TRUE()&\"1\"": "TRUE1", "TRUE<>FALSE()": "TRUE", "TRUE<>1&\"x\"": "TRUE", // Engineering Functions // BESSELI "BESSELI(4.5,1)": "15.3892227537359", "BESSELI(32,1)": "5502845511211.25", "BESSELI({32},1)": "5502845511211.25", "BESSELI(32,{1})": "5502845511211.25", "BESSELI({32},{1})": "5502845511211.25", // BESSELJ "BESSELJ(1.9,2)": "0.329925727692387", "BESSELJ({1.9},2)": "0.329925727692387", "BESSELJ(1.9,{2})": "0.329925727692387", "BESSELJ({1.9},{2})": "0.329925727692387", // BESSELK "BESSELK(0.05,0)": "3.11423403428966", "BESSELK(0.05,1)": "19.9096743272486", "BESSELK(0.05,2)": "799.501207124235", "BESSELK(3,2)": "0.0615104585619118", "BESSELK({3},2)": "0.0615104585619118", "BESSELK(3,{2})": "0.0615104585619118", "BESSELK({3},{2})": "0.0615104585619118", // BESSELY "BESSELY(0.05,0)": "-1.97931100684153", "BESSELY(0.05,1)": "-12.789855163794", "BESSELY(0.05,2)": "-509.61489554492", "BESSELY(9,2)": "-0.229082087487741", "BESSELY({9},2)": "-0.229082087487741", "BESSELY(9,{2})": "-0.229082087487741", "BESSELY({9},{2})": "-0.229082087487741", // BIN2DEC "BIN2DEC(\"10\")": "2", "BIN2DEC(\"11\")": "3", "BIN2DEC(\"0000000010\")": "2", "BIN2DEC(\"1111111110\")": "-2", "BIN2DEC(\"110\")": "6", "BIN2DEC({\"110\"})": "6", // BIN2HEX "BIN2HEX(\"10\")": "2", "BIN2HEX(\"0000000001\")": "1", "BIN2HEX(\"10\",10)": "0000000002", "BIN2HEX(\"1111111110\")": "FFFFFFFFFE", "BIN2HEX(\"11101\")": "1D", "BIN2HEX({\"11101\"})": "1D", // BIN2OCT "BIN2OCT(\"101\")": "5", "BIN2OCT(\"0000000001\")": "1", "BIN2OCT(\"10\",10)": "0000000002", "BIN2OCT(\"1111111110\")": "7777777776", "BIN2OCT(\"1110\")": "16", "BIN2OCT({\"1110\"})": "16", // BITAND "BITAND(13,14)": "12", "BITAND({13},14)": "12", "BITAND(13,{14})": "12", "BITAND({13},{14})": "12", // BITLSHIFT "BITLSHIFT(5,2)": "20", "BITLSHIFT({3},5)": "96", "BITLSHIFT(3,5)": "96", "BITLSHIFT(3,{5})": "96", "BITLSHIFT({3},{5})": "96", // BITOR "BITOR(9,12)": "13", "BITOR({9},12)": "13", "BITOR(9,{12})": "13", "BITOR({9},{12})": "13", // BITRSHIFT "BITRSHIFT(20,2)": "5", "BITRSHIFT(52,4)": "3", "BITRSHIFT({52},4)": "3", "BITRSHIFT(52,{4})": "3", "BITRSHIFT({52},{4})": "3", // BITXOR "BITXOR(5,6)": "3", "BITXOR(9,12)": "5", "BITXOR({9},12)": "5", "BITXOR(9,{12})": "5", "BITXOR({9},{12})": "5", // COMPLEX "COMPLEX(5,2)": "5+2i", "COMPLEX(5,-9)": "5-9i", "COMPLEX(-1,2,\"j\")": "-1+2j", "COMPLEX(10,-5,\"i\")": "10-5i", "COMPLEX(0,5)": "5i", "COMPLEX(3,0)": "3", "COMPLEX(0,-2)": "-2i", "COMPLEX(0,0)": "0", "COMPLEX(0,-1,\"j\")": "-j", // CONVERT "CONVERT(20.2,\"m\",\"yd\")": "22.0909886264217", "CONVERT(20.2,\"cm\",\"yd\")": "0.220909886264217", "CONVERT(0.2,\"gal\",\"tsp\")": "153.6", "CONVERT(5,\"gal\",\"l\")": "18.92705892", "CONVERT(0.02,\"Gm\",\"m\")": "20000000", "CONVERT(0,\"C\",\"F\")": "32", "CONVERT(1,\"ly^2\",\"ly^2\")": "1", "CONVERT(0.00194255938572296,\"sg\",\"ozm\")": "1", "CONVERT(5,\"kg\",\"kg\")": "5", "CONVERT(4.5359237E-01,\"kg\",\"lbm\")": "1", "CONVERT(0.2,\"kg\",\"hg\")": "2", "CONVERT(12.345000000000001,\"km\",\"m\")": "12345", "CONVERT(12345,\"m\",\"km\")": "12.345", "CONVERT(0.621371192237334,\"mi\",\"km\")": "1", "CONVERT(1.23450000000000E+05,\"ang\",\"um\")": "12.345", "CONVERT(1.23450000000000E+02,\"kang\",\"um\")": "12.345", "CONVERT(1000,\"dal\",\"hl\")": "100", "CONVERT(1,\"yd\",\"ft\")": "2.99999999999999", "CONVERT(20,\"C\",\"F\")": "68", "CONVERT(68,\"F\",\"C\")": "20", "CONVERT(293.15,\"K\",\"F\")": "68", "CONVERT(68,\"F\",\"K\")": "293.15", "CONVERT(-273.15,\"C\",\"K\")": "0", "CONVERT(-459.67,\"F\",\"K\")": "0", "CONVERT(295.65,\"K\",\"C\")": "22.5", "CONVERT(22.5,\"C\",\"K\")": "295.65", "CONVERT(1667.85,\"C\",\"K\")": "1941", "CONVERT(3034.13,\"F\",\"K\")": "1941", "CONVERT(3493.8,\"Rank\",\"K\")": "1941", "CONVERT(1334.28,\"Reau\",\"K\")": "1941", "CONVERT(1941,\"K\",\"Rank\")": "3493.8", "CONVERT(1941,\"K\",\"Reau\")": "1334.28", "CONVERT(123.45,\"K\",\"kel\")": "123.45", "CONVERT(123.45,\"C\",\"cel\")": "123.45", "CONVERT(123.45,\"F\",\"fah\")": "123.45", "CONVERT(16,\"bit\",\"byte\")": "2", "CONVERT(1,\"kbyte\",\"byte\")": "1000", "CONVERT(1,\"kibyte\",\"byte\")": "1024", // DEC2BIN "DEC2BIN(2)": "10", "DEC2BIN(3)": "11", "DEC2BIN(2,10)": "0000000010", "DEC2BIN(-2)": "1111111110", "DEC2BIN(6)": "110", // DEC2HEX "DEC2HEX(10)": "A", "DEC2HEX(31)": "1F", "DEC2HEX(16,10)": "0000000010", "DEC2HEX(-16)": "FFFFFFFFF0", "DEC2HEX(273)": "111", // DEC2OCT "DEC2OCT(8)": "10", "DEC2OCT(18)": "22", "DEC2OCT(8,10)": "0000000010", "DEC2OCT(-8)": "7777777770", "DEC2OCT(237)": "355", // DELTA "DELTA(5,4)": "0", "DELTA(1.00001,1)": "0", "DELTA(1.23,1.23)": "1", "DELTA(1)": "0", "DELTA(0)": "1", // ERF "ERF(1.5)": "0.966105146475311", "ERF(0,1.5)": "0.966105146475311", "ERF(1,2)": "0.152621472069238", // ERF.PRECISE "ERF.PRECISE(-1)": "-0.842700792949715", "ERF.PRECISE(1.5)": "0.966105146475311", // ERFC "ERFC(0)": "1", "ERFC(0.5)": "0.479500122186953", "ERFC(-1)": "1.84270079294971", // ERFC.PRECISE "ERFC.PRECISE(0)": "1", "ERFC.PRECISE(0.5)": "0.479500122186953", "ERFC.PRECISE(-1)": "1.84270079294971", // GESTEP "GESTEP(1.2,0.001)": "1", "GESTEP(0.05,0.05)": "1", "GESTEP(-0.00001,0)": "0", "GESTEP(-0.00001)": "0", // HEX2BIN "HEX2BIN(\"2\")": "10", "HEX2BIN(\"0000000001\")": "1", "HEX2BIN(\"2\",10)": "0000000010", "HEX2BIN(\"F0\")": "11110000", "HEX2BIN(\"1D\")": "11101", // HEX2DEC "HEX2DEC(\"A\")": "10", "HEX2DEC(\"1F\")": "31", "HEX2DEC(\"0000000010\")": "16", "HEX2DEC(\"FFFFFFFFF0\")": "-16", "HEX2DEC(\"111\")": "273", "HEX2DEC(\"\")": "0", // HEX2OCT "HEX2OCT(\"A\")": "12", "HEX2OCT(\"000000000F\")": "17", "HEX2OCT(\"8\",10)": "0000000010", "HEX2OCT(\"FFFFFFFFF8\")": "7777777770", "HEX2OCT(\"1F3\")": "763", "HEX2OCT({\"1F3\"})": "763", // IMABS "IMABS(\"2j\")": "2", "IMABS(\"-1+2i\")": "2.23606797749979", "IMABS(COMPLEX(-1,2,\"j\"))": "2.23606797749979", // IMAGINARY "IMAGINARY(\"5+2i\")": "2", "IMAGINARY(\"2-i\")": "-1", "IMAGINARY(6)": "0", "IMAGINARY(\"3i\")": "3", "IMAGINARY(\"4+i\")": "1", // IMARGUMENT "IMARGUMENT(\"5+2i\")": "0.380506377112365", "IMARGUMENT(\"2-i\")": "-0.463647609000806", "IMARGUMENT(6)": "0", // IMCONJUGATE "IMCONJUGATE(\"5+2i\")": "5-2i", "IMCONJUGATE(\"2-i\")": "2+i", "IMCONJUGATE(6)": "6", "IMCONJUGATE(\"3i\")": "-3i", "IMCONJUGATE(\"4+i\")": "4-i", // IMCOS "IMCOS(0)": "1", "IMCOS(0.5)": "0.877582561890373", "IMCOS(\"3+0.5i\")": "-1.11634124452615-0.0735369737112366i", // IMCOSH "IMCOSH(0.5)": "1.12762596520638", "IMCOSH(\"3+0.5i\")": "8.83520460650099+4.80282508274303i", "IMCOSH(\"2-i\")": "2.03272300701967-3.0518977991518i", "IMCOSH(COMPLEX(1,-1))": "0.833730025131149-0.988897705762865i", // IMCOT "IMCOT(0.5)": "1.83048772171245", "IMCOT(\"3+0.5i\")": "-0.479345578747373-2.01609252150623i", "IMCOT(\"2-i\")": "-0.171383612909185+0.821329797493852i", "IMCOT(COMPLEX(1,-1))": "0.217621561854403+0.868014142895925i", // IMCSC "IMCSC(\"j\")": "-0.850918128239322j", // IMCSCH "IMCSCH(COMPLEX(1,-1))": "0.303931001628426+0.621518017170428i", // IMDIV "IMDIV(\"5+2i\",\"1+i\")": "3.5-1.5i", "IMDIV(\"2+2i\",\"2+i\")": "1.2+0.4i", "IMDIV(COMPLEX(5,2),COMPLEX(0,1))": "2-5i", // IMEXP "IMEXP(0)": "1", "IMEXP(0.5)": "1.64872127070013", "IMEXP(\"1-2i\")": "-1.13120438375681-2.47172667200482i", "IMEXP(COMPLEX(1,-1))": "1.46869393991589-2.28735528717884i", // IMLN "IMLN(0.5)": "-0.693147180559945", "IMLN(\"3+0.5i\")": "1.11231177576217+0.165148677414627i", "IMLN(\"2-i\")": "0.80471895621705-0.463647609000806i", "IMLN(COMPLEX(1,-1))": "0.346573590279973-0.785398163397448i", // IMLOG10 "IMLOG10(0.5)": "-0.301029995663981", "IMLOG10(\"3+0.5i\")": "0.483070866369516+0.0717231592947926i", "IMLOG10(\"2-i\")": "0.349485002168009-0.201359598136687i", "IMLOG10(COMPLEX(1,-1))": "0.150514997831991-0.34109408846046i", // IMREAL "IMREAL(\"5+2i\")": "5", "IMREAL(\"2+2i\")": "2", "IMREAL(6)": "6", "IMREAL(\"3i\")": "0", "IMREAL(COMPLEX(4,1))": "4", // IMSEC "IMSEC(0.5)": "1.13949392732455", "IMSEC(\"3+0.5i\")": "-0.89191317974033+0.0587531781817398i", "IMSEC(\"2-i\")": "-0.41314934426694-0.687527438655479i", "IMSEC(COMPLEX(1,-1))": "0.498337030555187-0.591083841721045i", // IMSECH "IMSECH(0.5)": "0.886818883970074", "IMSECH(\"3+0.5i\")": "0.0873665779621303-0.0474925494901607i", "IMSECH(\"2-i\")": "0.151176298265577+0.226973675393722i", "IMSECH(COMPLEX(1,-1))": "0.498337030555187+0.591083841721045i", // IMSIN "IMSIN(0.5)": "0.479425538604203", "IMSIN(\"3+0.5i\")": "0.15913058529844-0.515880442452527i", "IMSIN(\"2-i\")": "1.40311925062204+0.489056259041294i", "IMSIN(COMPLEX(1,-1))": "1.29845758141598-0.634963914784736i", // IMSINH "IMSINH(-0)": "0", "IMSINH(0.5)": "0.521095305493747", "IMSINH(\"3+0.5i\")": "8.79151234349371+4.82669427481082i", "IMSINH(\"2-i\")": "1.95960104142161-3.16577851321617i", "IMSINH(COMPLEX(1,-1))": "0.634963914784736-1.29845758141598i", // IMSQRT "IMSQRT(\"i\")": "0.707106781186548+0.707106781186548i", "IMSQRT(\"2-i\")": "1.45534669022535-0.343560749722512i", "IMSQRT(\"5+2i\")": "2.27872385417085+0.438842116902254i", "IMSQRT(6)": "2.44948974278318", "IMSQRT(\"-2-4i\")": "1.11178594050284-1.79890743994787i", // IMSUB "IMSUB(\"5+i\",\"1+4i\")": "4-3i", "IMSUB(\"9+2i\",6)": "3+2i", "IMSUB(COMPLEX(5,2),COMPLEX(0,1))": "5+i", // IMSUM "IMSUM(\"1-i\",\"5+10i\",2)": "8+9i", "IMSUM(COMPLEX(5,2),COMPLEX(0,1))": "5+3i", // IMTAN "IMTAN(-0)": "0", "IMTAN(0.5)": "0.54630248984379", "IMTAN(\"3+0.5i\")": "-0.111621050771583+0.469469993425885i", "IMTAN(\"2-i\")": "-0.243458201185725-1.16673625724092i", "IMTAN(COMPLEX(1,-1))": "0.271752585319512-1.08392332733869i", // OCT2BIN "OCT2BIN(\"5\")": "101", "OCT2BIN(\"0000000001\")": "1", "OCT2BIN(\"2\",10)": "0000000010", "OCT2BIN(\"7777777770\")": "1111111000", "OCT2BIN(\"16\")": "1110", // OCT2DEC "OCT2DEC(\"10\")": "8", "OCT2DEC(\"22\")": "18", "OCT2DEC(\"0000000010\")": "8", "OCT2DEC(\"7777777770\")": "-8", "OCT2DEC(\"355\")": "237", // OCT2HEX "OCT2HEX(\"10\")": "8", "OCT2HEX(\"0000000007\")": "7", "OCT2HEX(\"10\",10)": "0000000008", "OCT2HEX(\"7777777770\")": "FFFFFFFFF8", "OCT2HEX(\"763\")": "1F3", // Math and Trigonometric Functions // ABS "ABS(-1)": "1", "ABS(-6.5)": "6.5", "ABS(6.5)": "6.5", "ABS(0)": "0", "ABS(2-4.5)": "2.5", "ABS(ABS(-1))": "1", // ACOS "ACOS(-1)": "3.14159265358979", "ACOS(0)": "1.5707963267949", "ACOS(ABS(0))": "1.5707963267949", // ACOSH "ACOSH(1)": "0", "ACOSH(2.5)": "1.56679923697241", "ACOSH(5)": "2.29243166956118", "ACOSH(ACOSH(5))": "1.47138332153668", // _xlfn.ACOT "_xlfn.ACOT(1)": "0.785398163397448", "_xlfn.ACOT(-2)": "2.67794504458899", "_xlfn.ACOT(0)": "1.5707963267949", "_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009", // _xlfn.ACOTH "_xlfn.ACOTH(-5)": "-0.202732554054082", "_xlfn.ACOTH(1.1)": "1.52226121886171", "_xlfn.ACOTH(2)": "0.549306144334055", "_xlfn.ACOTH(ABS(-2))": "0.549306144334055", // _xlfn.AGGREGATE "_xlfn.AGGREGATE(1,0,A1:A6)": "1.5", "_xlfn.AGGREGATE(2,0,A1:A6)": "4", "_xlfn.AGGREGATE(3,0,A1:A6)": "4", "_xlfn.AGGREGATE(4,0,A1:A6)": "3", "_xlfn.AGGREGATE(5,0,A1:A6)": "0", "_xlfn.AGGREGATE(6,0,A1:A6)": "0", "_xlfn.AGGREGATE(7,0,A1:A6)": "1.29099444873581", "_xlfn.AGGREGATE(8,0,A1:A6)": "1.11803398874989", "_xlfn.AGGREGATE(9,0,A1:A6)": "6", "_xlfn.AGGREGATE(10,0,A1:A6)": "1.66666666666667", "_xlfn.AGGREGATE(11,0,A1:A6)": "1.25", "_xlfn.AGGREGATE(12,0,A1:A6)": "1.5", "_xlfn.AGGREGATE(14,0,A1:A6,1)": "3", "_xlfn.AGGREGATE(15,0,A1:A6,1)": "0", "_xlfn.AGGREGATE(16,0,A1:A6,1)": "3", "_xlfn.AGGREGATE(17,0,A1:A6,1)": "0.75", "_xlfn.AGGREGATE(19,0,A1:A6,1)": "0.25", // ARABIC "_xlfn.ARABIC(\"IV\")": "4", "_xlfn.ARABIC(\"-IV\")": "-4", "_xlfn.ARABIC(\"MCXX\")": "1120", "_xlfn.ARABIC(\"\")": "0", "_xlfn.ARABIC(\" ll lc \")": "-50", // ASIN "ASIN(-1)": "-1.5707963267949", "ASIN(0)": "0", "ASIN(ASIN(0))": "0", // ASINH "ASINH(0)": "0", "ASINH(-0.5)": "-0.481211825059603", "ASINH(2)": "1.44363547517881", "ASINH(ASINH(0))": "0", // ATAN "ATAN(-1)": "-0.785398163397448", "ATAN(0)": "0", "ATAN(1)": "0.785398163397448", "ATAN(ATAN(0))": "0", // ATANH "ATANH(-0.8)": "-1.09861228866811", "ATANH(0)": "0", "ATANH(0.5)": "0.549306144334055", "ATANH(ATANH(0))": "0", // ATAN2 "ATAN2(1,1)": "0.785398163397448", "ATAN2(1,-1)": "-0.785398163397448", "ATAN2(4,0)": "0", "ATAN2(4,ATAN2(4,0))": "0", // BASE "BASE(12,2)": "1100", "BASE(12,2,8)": "00001100", "BASE(100000,16)": "186A0", "BASE(BASE(12,2),16)": "44C", // CEILING "CEILING(22.25,0.1)": "22.3", "CEILING(22.25,0.5)": "22.5", "CEILING(22.25,1)": "23", "CEILING(22.25,10)": "30", "CEILING(22.25,20)": "40", "CEILING(-22.25,-0.1)": "-22.3", "CEILING(-22.25,-1)": "-23", "CEILING(-22.25,-5)": "-25", "CEILING(22.25)": "23", "CEILING(CEILING(22.25,0.1),0.1)": "22.3", // _xlfn.CEILING.MATH "_xlfn.CEILING.MATH(15.25,1)": "16", "_xlfn.CEILING.MATH(15.25,0.1)": "15.3", "_xlfn.CEILING.MATH(15.25,5)": "20", "_xlfn.CEILING.MATH(-15.25,1)": "-15", "_xlfn.CEILING.MATH(-15.25,1,1)": "-15", // should be 16 "_xlfn.CEILING.MATH(-15.25,10)": "-10", "_xlfn.CEILING.MATH(-15.25)": "-15", "_xlfn.CEILING.MATH(-15.25,-5,-1)": "-10", "_xlfn.CEILING.MATH(_xlfn.CEILING.MATH(15.25,1),1)": "16", // _xlfn.CEILING.PRECISE "_xlfn.CEILING.PRECISE(22.25,0.1)": "22.3", "_xlfn.CEILING.PRECISE(22.25,0.5)": "22.5", "_xlfn.CEILING.PRECISE(22.25,1)": "23", "_xlfn.CEILING.PRECISE(22.25)": "23", "_xlfn.CEILING.PRECISE(22.25,10)": "30", "_xlfn.CEILING.PRECISE(22.25,0)": "0", "_xlfn.CEILING.PRECISE(-22.25,1)": "-22", "_xlfn.CEILING.PRECISE(-22.25,-1)": "-22", "_xlfn.CEILING.PRECISE(-22.25,5)": "-20", "_xlfn.CEILING.PRECISE(_xlfn.CEILING.PRECISE(22.25,0.1),5)": "25", // COMBIN "COMBIN(6,1)": "6", "COMBIN(6,2)": "15", "COMBIN(6,3)": "20", "COMBIN(6,4)": "15", "COMBIN(6,5)": "6", "COMBIN(6,6)": "1", "COMBIN(0,0)": "1", "COMBIN(6,COMBIN(0,0))": "6", // _xlfn.COMBINA "_xlfn.COMBINA(6,1)": "6", "_xlfn.COMBINA(6,2)": "21", "_xlfn.COMBINA(6,3)": "56", "_xlfn.COMBINA(6,4)": "126", "_xlfn.COMBINA(6,5)": "252", "_xlfn.COMBINA(6,6)": "462", "_xlfn.COMBINA(0,0)": "0", "_xlfn.COMBINA(0,_xlfn.COMBINA(0,0))": "0", // COS "COS(0.785398163)": "0.707106781467586", "COS(0)": "1", "-COS(0)": "-1", "COS(COS(0))": "0.54030230586814", // COSH "COSH(0)": "1", "COSH(0.5)": "1.12762596520638", "COSH(-2)": "3.76219569108363", "COSH(COSH(0))": "1.54308063481524", // _xlfn.COT "_xlfn.COT(0.785398163397448)": "1", "_xlfn.COT(_xlfn.COT(0.45))": "-0.545473116787229", // _xlfn.COTH "_xlfn.COTH(-3.14159265358979)": "-1.00374187319732", "_xlfn.COTH(_xlfn.COTH(1))": "1.15601401811395", // _xlfn.CSC "_xlfn.CSC(-6)": "3.57889954725441", "_xlfn.CSC(1.5707963267949)": "1", "_xlfn.CSC(_xlfn.CSC(1))": "1.07785184031088", // _xlfn.CSCH "_xlfn.CSCH(-3.14159265358979)": "-0.0865895375300472", "_xlfn.CSCH(_xlfn.CSCH(1))": "1.04451010395518", // _xlfn.DECIMAL "_xlfn.DECIMAL(\"1100\",2)": "12", "_xlfn.DECIMAL(\"186A0\",16)": "100000", "_xlfn.DECIMAL(\"31L0\",32)": "100000", "_xlfn.DECIMAL(\"70122\",8)": "28754", "_xlfn.DECIMAL(\"0x70122\",8)": "28754", // DEGREES "DEGREES(1)": "57.2957795130823", "DEGREES(2.5)": "143.239448782706", "DEGREES(DEGREES(1))": "3282.80635001174", // EVEN "EVEN(23)": "24", "EVEN(2.22)": "4", "EVEN(0)": "0", "EVEN(-0.3)": "-2", "EVEN(-11)": "-12", "EVEN(-4)": "-4", "EVEN((0))": "0", // EXP "EXP(100)": "2.68811714181614E+43", "EXP(0.1)": "1.10517091807565", "EXP(0)": "1", "EXP(-5)": "0.00673794699908547", "EXP(EXP(0))": "2.71828182845905", // FACT "FACT(3)": "6", "FACT(6)": "720", "FACT(10)": "3628800", "FACT(FACT(3))": "720", // FACTDOUBLE "FACTDOUBLE(5)": "15", "FACTDOUBLE(8)": "384", "FACTDOUBLE(13)": "135135", "FACTDOUBLE(FACTDOUBLE(1))": "1", // FLOOR "FLOOR(26.75,0.1)": "26.7", "FLOOR(26.75,0.5)": "26.5", "FLOOR(26.75,1)": "26", "FLOOR(26.75,10)": "20", "FLOOR(26.75,20)": "20", "FLOOR(-26.75,-0.1)": "-26.7", "FLOOR(-26.75,-1)": "-26", "FLOOR(-26.75,-5)": "-25", "FLOOR(-2.05,2)": "-4", "FLOOR(FLOOR(26.75,1),1)": "26", // _xlfn.FLOOR.MATH "_xlfn.FLOOR.MATH(58.55)": "58", "_xlfn.FLOOR.MATH(58.55,0.1)": "58.5", "_xlfn.FLOOR.MATH(58.55,5)": "55", "_xlfn.FLOOR.MATH(58.55,1,1)": "58", "_xlfn.FLOOR.MATH(-58.55,1)": "-59", "_xlfn.FLOOR.MATH(-58.55,1,-1)": "-58", "_xlfn.FLOOR.MATH(-58.55,1,1)": "-59", // should be -58 "_xlfn.FLOOR.MATH(-58.55,10)": "-60", "_xlfn.FLOOR.MATH(_xlfn.FLOOR.MATH(1),10)": "0", // _xlfn.FLOOR.PRECISE "_xlfn.FLOOR.PRECISE(26.75,0.1)": "26.7", "_xlfn.FLOOR.PRECISE(26.75,0.5)": "26.5", "_xlfn.FLOOR.PRECISE(26.75,1)": "26", "_xlfn.FLOOR.PRECISE(26.75)": "26", "_xlfn.FLOOR.PRECISE(26.75,10)": "20", "_xlfn.FLOOR.PRECISE(26.75,0)": "0", "_xlfn.FLOOR.PRECISE(-26.75,1)": "-27", "_xlfn.FLOOR.PRECISE(-26.75,-1)": "-27", "_xlfn.FLOOR.PRECISE(-26.75,-5)": "-30", "_xlfn.FLOOR.PRECISE(_xlfn.FLOOR.PRECISE(26.75),-5)": "25", // GCD "GCD(0)": "0", "GCD(1,0)": "1", "GCD(\"0\",1)": "1", "GCD(1,5)": "1", "GCD(15,10,25)": "5", "GCD(0,8,12)": "4", "GCD(7,2)": "1", "GCD(1,GCD(1))": "1", // INT "INT(100.9)": "100", "INT(5.22)": "5", "INT(5.99)": "5", "INT(-6.1)": "-7", "INT(-100.9)": "-101", "INT(INT(0))": "0", // ISO.CEILING "ISO.CEILING(22.25)": "23", "ISO.CEILING(22.25,1)": "23", "ISO.CEILING(22.25,0.1)": "22.3", "ISO.CEILING(22.25,10)": "30", "ISO.CEILING(-22.25,1)": "-22", "ISO.CEILING(-22.25,0.1)": "-22.2", "ISO.CEILING(-22.25,5)": "-20", "ISO.CEILING(-22.25,0)": "0", "ISO.CEILING(1,ISO.CEILING(1,0))": "0", // LCM "LCM(1,5)": "5", "LCM(15,10,25)": "150", "LCM(1,8,12)": "24", "LCM(7,2)": "14", "LCM(7)": "7", "LCM(\"\",1)": "1", "LCM(0,0)": "0", "LCM(0,LCM(0,0))": "0", // LN "LN(1)": "0", "LN(100)": "4.60517018598809", "LN(0.5)": "-0.693147180559945", "LN(LN(100))": "1.5271796258079", // LOG "LOG(64,2)": "6", "LOG(100)": "2", "LOG(4,0.5)": "-2", "LOG(500)": "2.69897000433602", "LOG(LOG(100))": "0.301029995663981", // LOG10 "LOG10(100)": "2", "LOG10(1000)": "3", "LOG10(0.001)": "-3", "LOG10(25)": "1.39794000867204", "LOG10(LOG10(100))": "0.301029995663981", // IMLOG2 "IMLOG2(\"5+2i\")": "2.42899049756379+0.548954663286635i", "IMLOG2(\"2-i\")": "1.16096404744368-0.668902106225488i", "IMLOG2(6)": "2.58496250072116", "IMLOG2(\"3i\")": "1.58496250072116+2.2661800709136i", "IMLOG2(\"4+i\")": "2.04373142062517+0.353429502416735i", // IMPOWER "IMPOWER(\"2-i\",2)": "3-4i", "IMPOWER(\"2-i\",3)": "2-11i", "IMPOWER(9,0.5)": "3", "IMPOWER(\"2+4i\",-2)": "-0.03-0.04i", // IMPRODUCT "IMPRODUCT(3,6)": "18", "IMPRODUCT(\"\",3,SUM(6))": "18", "IMPRODUCT(\"1-i\",\"5+10i\",2)": "30+10i", "IMPRODUCT(COMPLEX(5,2),COMPLEX(0,1))": "-2+5i", "IMPRODUCT(A1:C1)": "4", // MINVERSE "MINVERSE(A1:B2)": "-0", // MMULT "MMULT(0,0)": "0", "MMULT(2,4)": "8", "MMULT(A4:A4,A4:A4)": "0", // MOD "MOD(6,4)": "2", "MOD(6,3)": "0", "MOD(6,2.5)": "1", "MOD(6,1.333)": "0.668", "MOD(-10.23,1)": "0.77", "MOD(MOD(1,1),1)": "0", // MROUND "MROUND(333.7,0.5)": "333.5", "MROUND(333.8,1)": "334", "MROUND(333.3,2)": "334", "MROUND(555.3,400)": "400", "MROUND(555,1000)": "1000", "MROUND(-555.7,-1)": "-556", "MROUND(-555.4,-1)": "-555", "MROUND(-1555,-1000)": "-2000", "MROUND(MROUND(1,1),1)": "1", // MULTINOMIAL "MULTINOMIAL(3,1,2,5)": "27720", "MULTINOMIAL(\"\",3,1,2,5)": "27720", "MULTINOMIAL(MULTINOMIAL(1))": "1", // _xlfn.MUNIT "_xlfn.MUNIT(4)": "1", // ODD "ODD(22)": "23", "ODD(1.22)": "3", "ODD(1.22+4)": "7", "ODD(0)": "1", "ODD(-1.3)": "-3", "ODD(-10)": "-11", "ODD(-3)": "-3", "ODD(ODD(1))": "1", // PI "PI()": "3.14159265358979", // POWER "POWER(4,2)": "16", "POWER(4,POWER(1,1))": "4", // PRODUCT "PRODUCT(3,6)": "18", "PRODUCT(\"3\",\"6\")": "18", "PRODUCT(PRODUCT(1),3,6)": "18", "PRODUCT(C1:C2)": "1", // QUOTIENT "QUOTIENT(5,2)": "2", "QUOTIENT(4.5,3.1)": "1", "QUOTIENT(-10,3)": "-3", "QUOTIENT(QUOTIENT(1,2),3)": "0", // RADIANS "RADIANS(50)": "0.872664625997165", "RADIANS(-180)": "-3.14159265358979", "RADIANS(180)": "3.14159265358979", "RADIANS(360)": "6.28318530717959", "RADIANS(RADIANS(360))": "0.109662271123215", // ROMAN "ROMAN(499,0)": "CDXCIX", "ROMAN(1999,0)": "MCMXCIX", "ROMAN(1999,1)": "MLMVLIV", "ROMAN(1999,2)": "MXMIX", "ROMAN(1999,3)": "MVMIV", "ROMAN(1999,4)": "MIM", "ROMAN(1999,-1)": "MCMXCIX", "ROMAN(1999,5)": "MIM", "ROMAN(1999,ODD(1))": "MLMVLIV", // ROUND "ROUND(100.319,1)": "100.3", "ROUND(5.28,1)": "5.3", "ROUND(5.9999,3)": "6", "ROUND(99.5,0)": "100", "ROUND(-6.3,0)": "-6", "ROUND(-100.5,0)": "-101", "ROUND(-22.45,1)": "-22.5", "ROUND(999,-1)": "1000", "ROUND(991,-1)": "990", "ROUND(ROUND(100,1),-1)": "100", // ROUNDDOWN "ROUNDDOWN(99.999,1)": "99.9", "ROUNDDOWN(99.999,2)": "99.99", "ROUNDDOWN(99.999,0)": "99", "ROUNDDOWN(99.999,-1)": "90", "ROUNDDOWN(-99.999,2)": "-99.99", "ROUNDDOWN(-99.999,-1)": "-90", "ROUNDDOWN(ROUNDDOWN(100,1),-1)": "100", // ROUNDUP "ROUNDUP(11.111,1)": "11.2", "ROUNDUP(11.111,2)": "11.12", "ROUNDUP(11.111,0)": "12", "ROUNDUP(11.111,-1)": "20", "ROUNDUP(-11.111,2)": "-11.12", "ROUNDUP(-11.111,-1)": "-20", "ROUNDUP(ROUNDUP(100,1),-1)": "100", // SEARCH "SEARCH(\"s\",F1)": "1", "SEARCH(\"s\",F1,2)": "5", "SEARCH(\"e\",F1)": "4", "SEARCH(\"e*\",F1)": "4", "SEARCH(\"?e\",F1)": "3", "SEARCH(\"??e\",F1)": "2", "SEARCH(6,F2)": "2", "SEARCH(\"?\",\"你好world\")": "1", "SEARCH(\"?l\",\"你好world\")": "5", "SEARCH(\"?+\",\"你好 1+2\")": "4", "SEARCH(\" ?+\",\"你好 1+2\")": "3", // SEARCHB "SEARCHB(\"s\",F1)": "1", "SEARCHB(\"s\",F1,2)": "5", "SEARCHB(\"e\",F1)": "4", "SEARCHB(\"e*\",F1)": "4", "SEARCHB(\"?e\",F1)": "3", "SEARCHB(\"??e\",F1)": "2", "SEARCHB(6,F2)": "2", "SEARCHB(\"?\",\"你好world\")": "5", "SEARCHB(\"?l\",\"你好world\")": "7", "SEARCHB(\"?+\",\"你好 1+2\")": "6", "SEARCHB(\" ?+\",\"你好 1+2\")": "5", // SEC "_xlfn.SEC(-3.14159265358979)": "-1", "_xlfn.SEC(0)": "1", "_xlfn.SEC(_xlfn.SEC(0))": "0.54030230586814", // SECH "_xlfn.SECH(-3.14159265358979)": "0.0862667383340547", "_xlfn.SECH(0)": "1", "_xlfn.SECH(_xlfn.SECH(0))": "0.648054273663885", // SERIESSUM "SERIESSUM(1,2,3,A1:A4)": "6", "SERIESSUM(1,2,3,A1:B5)": "15", // SIGN "SIGN(9.5)": "1", "SIGN(-9.5)": "-1", "SIGN(0)": "0", "SIGN(0.00000001)": "1", "SIGN(6-7)": "-1", "SIGN(SIGN(-1))": "-1", // SIN "SIN(0.785398163)": "0.707106780905509", "SIN(SIN(1))": "0.745624141665558", // SINH "SINH(0)": "0", "SINH(0.5)": "0.521095305493747", "SINH(-2)": "-3.62686040784702", "SINH(SINH(0))": "0", // SQRT "SQRT(4)": "2", "SQRT(SQRT(16))": "2", // SQRTPI "SQRTPI(5)": "3.96332729760601", "SQRTPI(0.2)": "0.792665459521202", "SQRTPI(100)": "17.7245385090552", "SQRTPI(0)": "0", "SQRTPI(SQRTPI(0))": "0", // STDEV "STDEV(F2:F9)": "10724.9782875238", "STDEV(MUNIT(2))": "0.577350269189626", "STDEV(0,INT(0))": "0", "STDEV(INT(1),INT(1))": "0", // STDEV.S "STDEV.S(F2:F9)": "10724.9782875238", // STDEVA "STDEVA(F2:F9)": "10724.9782875238", "STDEVA(MUNIT(2))": "0.577350269189626", "STDEVA(0,INT(0))": "0", // POISSON.DIST "POISSON.DIST(20,25,FALSE)": "0.0519174686084913", "POISSON.DIST(35,40,TRUE)": "0.242414197690103", // POISSON "POISSON(20,25,FALSE)": "0.0519174686084913", "POISSON(35,40,TRUE)": "0.242414197690103", // SUBTOTAL "SUBTOTAL(1,A1:A6)": "1.5", "SUBTOTAL(2,A1:A6)": "4", "SUBTOTAL(3,A1:A6)": "4", "SUBTOTAL(4,A1:A6)": "3", "SUBTOTAL(5,A1:A6)": "0", "SUBTOTAL(6,A1:A6)": "0", "SUBTOTAL(7,A1:A6)": "1.29099444873581", "SUBTOTAL(8,A1:A6)": "1.11803398874989", "SUBTOTAL(9,A1:A6)": "6", "SUBTOTAL(10,A1:A6)": "1.66666666666667", "SUBTOTAL(11,A1:A6)": "1.25", "SUBTOTAL(101,A1:A6)": "1.5", "SUBTOTAL(102,A1:A6)": "4", "SUBTOTAL(103,A1:A6)": "4", "SUBTOTAL(104,A1:A6)": "3", "SUBTOTAL(105,A1:A6)": "0", "SUBTOTAL(106,A1:A6)": "0", "SUBTOTAL(107,A1:A6)": "1.29099444873581", "SUBTOTAL(108,A1:A6)": "1.11803398874989", "SUBTOTAL(109,A1:A6)": "6", "SUBTOTAL(109,A1:A6,A1:A6)": "12", "SUBTOTAL(110,A1:A6)": "1.66666666666667", "SUBTOTAL(111,A1:A6)": "1.25", "SUBTOTAL(111,A1:A6,A1:A6)": "1.25", // SUM "SUM(1,2)": "3", "SUM(\"1\",\"2\")": "3", "SUM(\"\",1,2)": "3", "SUM(1,2+3)": "6", "SUM(SUM(1,2),2)": "5", "(-2-SUM(-4+7))*5": "-25", "SUM(1,2,3,4,5,6,7)": "28", "SUM(1,2)+SUM(1,2)": "6", "1+SUM(SUM(1,2*3),4)": "12", "1+SUM(SUM(1,-2*3),4)": "0", "(-2-SUM(-4*(7+7)))*5": "270", "SUM(SUM(1+2/1)*2-3/2,2)": "6.5", "((3+5*2)+3)/5+(-6)/4*2+3": "3.2", "1+SUM(SUM(1,2*3),4)*-4/2+5+(4+2)*3": "2", "1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.6666666666667", "SUM(1+ROW())": "2", "SUM((SUM(2))+1)": "3", "IF(2<0, 1, (4))": "4", "IF(2>0, (1), 4)": "1", "IF(2>0, (A1)*2.5, 4)": "2.5", "SUM({1,2,3,4,\"\"})": "10", // SUMIF "SUMIF(F1:F5, \"\")": "0", "SUMIF(A1:A5, \"3\")": "3", "SUMIF(F1:F5, \"=36693\")": "36693", "SUMIF(F1:F5, \"<100\")": "0", "SUMIF(F1:F5, \"<=36693\")": "93233", "SUMIF(F1:F5, \">100\")": "146554", "SUMIF(F1:F5, \">=100\")": "146554", "SUMIF(F1:F5, \">=text\")": "0", "SUMIF(F1:F5, \"*Jan\",F2:F5)": "0", "SUMIF(D3:D7,\"Jan\",F2:F5)": "112114", "SUMIF(D2:D9,\"Feb\",F2:F9)": "157559", "SUMIF(E2:E9,\"North 1\",F2:F9)": "66582", "SUMIF(E2:E9,\"North*\",F2:F9)": "138772", "SUMIF(D1:D3,\"Month\",D1:D3)": "0", // SUMPRODUCT "SUMPRODUCT(A1,B1)": "4", "SUMPRODUCT(A1:A2,B1:B2)": "14", "SUMPRODUCT(A1:A3,B1:B3)": "14", "SUMPRODUCT(A1:B3)": "15", "SUMPRODUCT(A1:A3,B1:B3,B2:B4)": "20", // SUMSQ "SUMSQ(A1:A4)": "14", "SUMSQ(A1,B1,A2,B2,6)": "82", "SUMSQ(\"\",A1,B1,A2,B2,6)": "82", "SUMSQ(1,SUMSQ(1))": "2", "SUMSQ(\"1\",SUMSQ(1))": "2", "SUMSQ(MUNIT(3))": "3", // SUMX2MY2 "SUMX2MY2(A1:A4,B1:B4)": "-36", // SUMX2PY2 "SUMX2PY2(A1:A4,B1:B4)": "46", // SUMXMY2 "SUMXMY2(A1:A4,B1:B4)": "18", // TAN "TAN(1.047197551)": "1.73205080678249", "TAN(0)": "0", "TAN(TAN(0))": "0", // TANH "TANH(0)": "0", "TANH(0.5)": "0.46211715726001", "TANH(-2)": "-0.964027580075817", "TANH(TANH(0))": "0", // TRUNC "TRUNC(99.999,1)": "99.9", "TRUNC(99.999,2)": "99.99", "TRUNC(99.999)": "99", "TRUNC(99.999,-1)": "90", "TRUNC(-99.999,2)": "-99.99", "TRUNC(-99.999,-1)": "-90", "TRUNC(TRUNC(1),-1)": "0", // Statistical Functions // AVEDEV "AVEDEV(1,2)": "0.5", "AVERAGE(A1:A4,B1:B4)": "2.5", // AVERAGE "AVERAGE(INT(1))": "1", "AVERAGE(A1)": "1", "AVERAGE(A1:A2)": "1.5", "AVERAGE(D2:F9)": "38014.125", // AVERAGEA "AVERAGEA(INT(1))": "1", "AVERAGEA(A1)": "1", "AVERAGEA(\"1\")": "1", "AVERAGEA(A1:A2)": "1.5", "AVERAGEA(D2:F9)": "12671.375", // BETA.DIST "BETA.DIST(0.4,4,5,TRUE,0,1)": "0.4059136", "BETA.DIST(0.6,4,5,FALSE,0,1)": "1.548288", // BETADIST "BETADIST(0.4,4,5)": "0.4059136", "BETADIST(0.4,4,5,0,1)": "0.4059136", "BETADIST(0.4,4,5,0.4,1)": "0", "BETADIST(1,2,2,1,3)": "0", "BETADIST(0.4,4,5,0.2,0.4)": "1", "BETADIST(0.4,4,1)": "0.0256", "BETADIST(0.4,1,5)": "0.92224", "BETADIST(3,4,6,2,4)": "0.74609375", "BETADIST(0.4,2,100)": "1", "BETADIST(0.75,3,4)": "0.96240234375", "BETADIST(0.2,0.7,4)": "0.71794309318323", "BETADIST(0.01,3,4)": "1.955359E-05", "BETADIST(0.75,130,140)": "1", // BETAINV "BETAINV(0.2,4,5,0,1)": "0.303225844664082", // BETA.INV "BETA.INV(0.2,4,5,0,1)": "0.303225844664082", // BINOMDIST "BINOMDIST(10,100,0.5,FALSE)": "1.36554263874631E-17", "BINOMDIST(50,100,0.5,FALSE)": "0.0795892373871787", "BINOMDIST(65,100,0.5,FALSE)": "0.000863855665741652", "BINOMDIST(10,100,0.5,TRUE)": "1.53164508771899E-17", "BINOMDIST(50,100,0.5,TRUE)": "0.539794618693589", "BINOMDIST(65,100,0.5,TRUE)": "0.999105034804256", // BINOM.DIST "BINOM.DIST(10,100,0.5,FALSE)": "1.36554263874631E-17", "BINOM.DIST(50,100,0.5,FALSE)": "0.0795892373871787", "BINOM.DIST(65,100,0.5,FALSE)": "0.000863855665741652", "BINOM.DIST(10,100,0.5,TRUE)": "1.53164508771899E-17", "BINOM.DIST(50,100,0.5,TRUE)": "0.539794618693589", "BINOM.DIST(65,100,0.5,TRUE)": "0.999105034804256", // BINOM.DIST.RANGE "BINOM.DIST.RANGE(100,0.5,0,40)": "0.0284439668204904", "BINOM.DIST.RANGE(100,0.5,45,55)": "0.728746975926165", "BINOM.DIST.RANGE(100,0.5,50,100)": "0.539794618693589", "BINOM.DIST.RANGE(100,0.5,50)": "0.0795892373871787", // BINOM.INV "BINOM.INV(0,0.5,0.75)": "0", "BINOM.INV(0.1,0.1,0.75)": "0", "BINOM.INV(0.6,0.4,0.75)": "0", "BINOM.INV(2,0.4,0.75)": "1", "BINOM.INV(100,0.5,20%)": "46", "BINOM.INV(100,0.5,50%)": "50", "BINOM.INV(100,0.5,90%)": "56", // CHIDIST "CHIDIST(0.5,3)": "0.918891411654676", "CHIDIST(8,3)": "0.0460117056892314", "CHIDIST(40,4)": "4.32842260712097E-08", "CHIDIST(42,4)": "1.66816329414062E-08", // CHIINV "CHIINV(0.5,1)": "0.454936423119572", "CHIINV(0.75,1)": "0.101531044267622", "CHIINV(0.1,2)": "4.60517018598809", "CHIINV(0.8,2)": "0.446287102628419", // CHISQ.DIST "CHISQ.DIST(0,2,TRUE)": "0", "CHISQ.DIST(4,1,TRUE)": "0.954499736103642", "CHISQ.DIST(1180,1180,FALSE)": "0.00821093706387967", "CHISQ.DIST(2,1,FALSE)": "0.103776874355149", "CHISQ.DIST(3,2,FALSE)": "0.111565080074215", "CHISQ.DIST(2,3,FALSE)": "0.207553748710297", "CHISQ.DIST(1425,1,FALSE)": "3.88315098887099E-312", "CHISQ.DIST(3,2,TRUE)": "0.77686983985157", // CHISQ.DIST.RT "CHISQ.DIST.RT(0.5,3)": "0.918891411654676", "CHISQ.DIST.RT(8,3)": "0.0460117056892314", "CHISQ.DIST.RT(40,4)": "4.32842260712097E-08", "CHISQ.DIST.RT(42,4)": "1.66816329414062E-08", // CHISQ.INV "CHISQ.INV(0,2)": "0", "CHISQ.INV(0.75,1)": "1.32330369693147", "CHISQ.INV(0.1,2)": "0.210721031315653", "CHISQ.INV(0.8,2)": "3.2188758248682", "CHISQ.INV(0.25,3)": "1.21253290304567", // CHISQ.INV.RT "CHISQ.INV.RT(0.75,1)": "0.101531044267622", "CHISQ.INV.RT(0.1,2)": "4.60517018598809", "CHISQ.INV.RT(0.8,2)": "0.446287102628419", // CONFIDENCE "CONFIDENCE(0.05,0.07,100)": "0.0137197479028414", // CONFIDENCE.NORM "CONFIDENCE.NORM(0.05,0.07,100)": "0.0137197479028414", // CONFIDENCE.T "CONFIDENCE.T(0.05,0.07,100)": "0.0138895186611049", // CORREL "CORREL(A1:A5,B1:B5)": "1", // COUNT "COUNT()": "0", "COUNT(E1:F2,\"text\",1,INT(2),\"0\")": "4", // COUNTA "COUNTA()": "0", "COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8", "COUNTA(COUNTA(1),MUNIT(1))": "2", "COUNTA(D1:D2)": "2", // COUNTBLANK "COUNTBLANK(MUNIT(1))": "0", "COUNTBLANK(1)": "0", "COUNTBLANK(B1:C1)": "1", "COUNTBLANK(C1)": "0", // COUNTIF "COUNTIF(D1:D9,\"Jan\")": "4", "COUNTIF(D1:D9,\"<>Jan\")": "5", "COUNTIF(A1:F9,\">=50000\")": "2", "COUNTIF(A1:F9,TRUE)": "0", // COUNTIFS "COUNTIFS(A1:A9,2,D1:D9,\"Jan\")": "1", "COUNTIFS(F1:F9,\">20000\",D1:D9,\"Jan\")": "4", "COUNTIFS(F1:F9,\">60000\",D1:D9,\"Jan\")": "0", // CRITBINOM "CRITBINOM(0,0.5,0.75)": "0", "CRITBINOM(0.1,0.1,0.75)": "0", "CRITBINOM(0.6,0.4,0.75)": "0", "CRITBINOM(2,0.4,0.75)": "1", "CRITBINOM(100,0.5,20%)": "46", "CRITBINOM(100,0.5,50%)": "50", "CRITBINOM(100,0.5,90%)": "56", // DEVSQ "DEVSQ(1,3,5,2,9,7)": "47.5", "DEVSQ(A1:D2)": "10", // FISHER "FISHER(-0.9)": "-1.47221948958322", "FISHER(-0.25)": "-0.255412811882995", "FISHER(0.8)": "1.09861228866811", "FISHER(\"0.8\")": "1.09861228866811", "FISHER(INT(0))": "0", // FISHERINV "FISHERINV(-0.2)": "-0.197375320224904", "FISHERINV(INT(0))": "0", "FISHERINV(\"0\")": "0", "FISHERINV(2.8)": "0.992631520201128", // FORECAST "FORECAST(7,A1:A7,B1:B7)": "4", // FORECAST.LINEAR "FORECAST.LINEAR(7,A1:A7,B1:B7)": "4", // FREQUENCY "SUM(FREQUENCY(A2,B2))": "1", "SUM(FREQUENCY(A1:A5,B1:B2))": "4", // GAMMA "GAMMA(0.1)": "9.51350769866873", "GAMMA(INT(1))": "1", "GAMMA(1.5)": "0.886226925452758", "GAMMA(5.5)": "52.3427777845535", "GAMMA(\"5.5\")": "52.3427777845535", // GAMMA.DIST "GAMMA.DIST(6,3,2,FALSE)": "0.112020903827694", "GAMMA.DIST(6,3,2,TRUE)": "0.576809918873156", // GAMMADIST "GAMMADIST(6,3,2,FALSE)": "0.112020903827694", "GAMMADIST(6,3,2,TRUE)": "0.576809918873156", // GAMMA.INV "GAMMA.INV(0.5,3,2)": "5.34812062744712", "GAMMA.INV(0.5,0.5,1)": "0.227468211559786", // GAMMAINV "GAMMAINV(0.5,3,2)": "5.34812062744712", "GAMMAINV(0.5,0.5,1)": "0.227468211559786", // GAMMALN "GAMMALN(4.5)": "2.45373657084244", "GAMMALN(INT(1))": "0", // GAMMALN.PRECISE "GAMMALN.PRECISE(0.4)": "0.796677817701784", "GAMMALN.PRECISE(4.5)": "2.45373657084244", // GAUSS "GAUSS(-5)": "-0.499999713348428", "GAUSS(0)": "0", "GAUSS(\"0\")": "0", "GAUSS(0.1)": "0.039827837277029", "GAUSS(2.5)": "0.493790334674224", // GEOMEAN "GEOMEAN(2.5,3,0.5,1,3)": "1.6226711115996", // HARMEAN "HARMEAN(2.5,3,0.5,1,3)": "1.22950819672131", "HARMEAN(\"2.5\",3,0.5,1,INT(3),\"\")": "1.22950819672131", // HYPGEOM.DIST "HYPGEOM.DIST(0,3,3,9,TRUE)": "0.238095238095238", "HYPGEOM.DIST(1,3,3,9,TRUE)": "0.773809523809524", "HYPGEOM.DIST(2,3,3,9,TRUE)": "0.988095238095238", "HYPGEOM.DIST(3,3,3,9,TRUE)": "1", "HYPGEOM.DIST(1,4,4,12,FALSE)": "0.452525252525253", "HYPGEOM.DIST(2,4,4,12,FALSE)": "0.339393939393939", "HYPGEOM.DIST(3,4,4,12,FALSE)": "0.0646464646464646", "HYPGEOM.DIST(4,4,4,12,FALSE)": "0.00202020202020202", // HYPGEOMDIST "HYPGEOMDIST(1,4,4,12)": "0.452525252525253", "HYPGEOMDIST(2,4,4,12)": "0.339393939393939", "HYPGEOMDIST(3,4,4,12)": "0.0646464646464646", "HYPGEOMDIST(4,4,4,12)": "0.00202020202020202", // INTERCEPT "INTERCEPT(A1:A4,B1:B4)": "-3", // KURT "KURT(F1:F9)": "-1.03350350255137", "KURT(F1,F2:F9)": "-1.03350350255137", "KURT(INT(1),MUNIT(2))": "-3.33333333333334", // EXPON.DIST "EXPON.DIST(0.5,1,TRUE)": "0.393469340287367", "EXPON.DIST(0.5,1,FALSE)": "0.606530659712633", "EXPON.DIST(2,1,TRUE)": "0.864664716763387", // EXPONDIST "EXPONDIST(0.5,1,TRUE)": "0.393469340287367", "EXPONDIST(0.5,1,FALSE)": "0.606530659712633", "EXPONDIST(2,1,TRUE)": "0.864664716763387", // FDIST "FDIST(5,1,2)": "0.154845745271483", // F.DIST "F.DIST(1,2,5,TRUE)": "0.568798849628308", "F.DIST(1,2,5,FALSE)": "0.308000821694066", // F.DIST.RT "F.DIST.RT(5,1,2)": "0.154845745271483", // F.INV "F.INV(0.9,2,5)": "3.77971607877395", // FINV "FINV(0.2,1,2)": "3.55555555555555", "FINV(0.6,1,2)": "0.380952380952381", "FINV(0.6,2,2)": "0.666666666666667", "FINV(0.6,4,4)": "0.763454070045235", "FINV(0.5,4,8)": "0.914645355977072", "FINV(0.1,79,86)": "1.32646097270444", "FINV(1,40,5)": "0", // F.INV.RT "F.INV.RT(0.2,1,2)": "3.55555555555555", "F.INV.RT(0.6,1,2)": "0.380952380952381", "F.INV.RT(0.6,2,2)": "0.666666666666667", "F.INV.RT(0.6,4,4)": "0.763454070045235", "F.INV.RT(0.5,4,8)": "0.914645355977072", "F.INV.RT(0.1,79,86)": "1.32646097270444", "F.INV.RT(1,40,5)": "0", // LOGINV "LOGINV(0.3,2,0.2)": "6.6533460753367", // LOGINV "LOGNORM.INV(0.3,2,0.2)": "6.6533460753367", // LOGNORM.DIST "LOGNORM.DIST(0.5,10,5,FALSE)": "0.0162104821842127", "LOGNORM.DIST(12,10,5,TRUE)": "0.0664171147992078", // LOGNORMDIST "LOGNORMDIST(12,10,5)": "0.0664171147992078", // NEGBINOM.DIST "NEGBINOM.DIST(6,12,0.5,FALSE)": "0.047210693359375", "NEGBINOM.DIST(12,12,0.5,FALSE)": "0.0805901288986206", "NEGBINOM.DIST(15,12,0.5,FALSE)": "0.057564377784729", "NEGBINOM.DIST(12,12,0.5,TRUE)": "0.58059012889862", "NEGBINOM.DIST(15,12,0.5,TRUE)": "0.778965830802917", // NEGBINOMDIST "NEGBINOMDIST(6,12,0.5)": "0.047210693359375", "NEGBINOMDIST(12,12,0.5)": "0.0805901288986206", "NEGBINOMDIST(15,12,0.5)": "0.057564377784729", // NORM.DIST "NORM.DIST(0.8,1,0.3,TRUE)": "0.252492537546923", "NORM.DIST(50,40,20,FALSE)": "0.017603266338215", // NORMDIST "NORMDIST(0.8,1,0.3,TRUE)": "0.252492537546923", "NORMDIST(50,40,20,FALSE)": "0.017603266338215", // NORM.INV "NORM.INV(0.6,5,2)": "5.50669420572", // NORMINV "NORMINV(0.6,5,2)": "5.50669420572", "NORMINV(0.99,40,1.5)": "43.489521811582", "NORMINV(0.02,40,1.5)": "36.9193766364954", // NORM.S.DIST "NORM.S.DIST(0.8,TRUE)": "0.788144601416603", // NORMSDIST "NORMSDIST(1.333333)": "0.908788725604095", "NORMSDIST(0)": "0.5", // NORM.S.INV "NORM.S.INV(0.25)": "-0.674489750223423", // NORMSINV "NORMSINV(0.25)": "-0.674489750223423", // LARGE "LARGE(A1:A5,1)": "3", "LARGE(A1:B5,2)": "4", "LARGE(A1,1)": "1", "LARGE(A1:F2,1)": "36693", // MAX "MAX(1)": "1", "MAX(TRUE())": "1", "MAX(0.5,TRUE())": "1", "MAX(FALSE())": "0", "MAX(MUNIT(2))": "1", "MAX(INT(1))": "1", "MAX(\"0\",\"2\")": "2", // MAXA "MAXA(1)": "1", "MAXA(TRUE())": "1", "MAXA(0.5,TRUE())": "1", "MAXA(FALSE())": "0", "MAXA(MUNIT(2))": "1", "MAXA(INT(1))": "1", "MAXA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "36693", // MAXIFS "MAXIFS(F2:F4,A2:A4,\">0\")": "36693", // MEDIAN "MEDIAN(A1:A5,12)": "2", "MEDIAN(A1:A5)": "1.5", "MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2", "MEDIAN(\"0\",\"2\")": "1", // MIN "MIN(1)": "1", "MIN(TRUE())": "1", "MIN(0.5,FALSE())": "0", "MIN(FALSE())": "0", "MIN(MUNIT(2))": "0", "MIN(INT(1))": "1", "MIN(2,\"1\")": "1", // MINA "MINA(1)": "1", "MINA(TRUE())": "1", "MINA(0.5,FALSE())": "0", "MINA(FALSE())": "0", "MINA(MUNIT(2))": "0", "MINA(INT(1))": "1", "MINA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "0", // MINIFS "MINIFS(F2:F4,A2:A4,\">0\")": "22100", // PEARSON "PEARSON(A1:A4,B1:B4)": "1", // PERCENTILE.EXC "PERCENTILE.EXC(A1:A4,0.2)": "0", "PERCENTILE.EXC(A1:A4,0.6)": "2", // PERCENTILE.INC "PERCENTILE.INC(A1:A4,0.2)": "0.6", // PERCENTILE "PERCENTILE(A1:A4,0.2)": "0.6", "PERCENTILE(0,0)": "0", // PERCENTRANK.EXC "PERCENTRANK.EXC(A1:B4,0)": "0.142", "PERCENTRANK.EXC(A1:B4,2)": "0.428", "PERCENTRANK.EXC(A1:B4,2.5)": "0.5", "PERCENTRANK.EXC(A1:B4,2.6,1)": "0.5", "PERCENTRANK.EXC(A1:B4,5)": "0.857", // PERCENTRANK.INC "PERCENTRANK.INC(A1:B4,0)": "0", "PERCENTRANK.INC(A1:B4,2)": "0.4", "PERCENTRANK.INC(A1:B4,2.5)": "0.5", "PERCENTRANK.INC(A1:B4,2.6,1)": "0.5", "PERCENTRANK.INC(A1:B4,5)": "1", // PERCENTRANK "PERCENTRANK(A1:B4,0)": "0", "PERCENTRANK(A1:B4,2)": "0.4", "PERCENTRANK(A1:B4,2.5)": "0.5", "PERCENTRANK(A1:B4,2.6,1)": "0.5", "PERCENTRANK(A1:B4,5)": "1", // PERMUT "PERMUT(6,6)": "720", "PERMUT(7,6)": "5040", "PERMUT(10,6)": "151200", // PERMUTATIONA "PERMUTATIONA(6,6)": "46656", "PERMUTATIONA(7,6)": "117649", // PHI "PHI(-1.5)": "0.129517595665892", "PHI(0)": "0.398942280401433", "PHI(0.1)": "0.396952547477012", "PHI(1)": "0.241970724519143", // QUARTILE "QUARTILE(A1:A4,2)": "1.5", // QUARTILE.EXC "QUARTILE.EXC(A1:A4,1)": "0.25", "QUARTILE.EXC(A1:A4,2)": "1.5", "QUARTILE.EXC(A1:A4,3)": "2.75", // QUARTILE.INC "QUARTILE.INC(A1:A4,0)": "0", // RANK "RANK(1,A1:B5)": "5", "RANK(1,A1:B5,0)": "5", "RANK(1,A1:B5,1)": "2", // RANK.EQ "RANK.EQ(1,A1:B5)": "5", "RANK.EQ(1,A1:B5,0)": "5", "RANK.EQ(1,A1:B5,1)": "2", // RSQ "RSQ(A1:A4,B1:B4)": "1", // SKEW "SKEW(1,2,3,4,3)": "-0.404796008910937", "SKEW(A1:B2)": "0", "SKEW(A1:D3)": "0", // SKEW.P "SKEW.P(1,2,3,4,3)": "-0.27154541788364", "SKEW.P(A1:B2)": "0", "SKEW.P(A1:D3)": "0", // SLOPE "SLOPE(A1:A4,B1:B4)": "1", // SMALL "SMALL(A1:A5,1)": "0", "SMALL(A1:B5,2)": "1", "SMALL(A1,1)": "1", "SMALL(A1:F2,1)": "1", // STANDARDIZE "STANDARDIZE(5.5,5,2)": "0.25", "STANDARDIZE(12,15,1.5)": "-2", "STANDARDIZE(-2,0,5)": "-0.4", // STDEVP "STDEVP(A1:B2,6,-1)": "2.40947204913349", // STDEV.P "STDEV.P(A1:B2,6,-1)": "2.40947204913349", // STDEVPA "STDEVPA(1,3,5,2)": "1.4790199457749", "STDEVPA(1,3,5,2,1,0)": "1.63299316185545", "STDEVPA(1,3,5,2,TRUE,\"text\")": "1.63299316185545", // T.DIST "T.DIST(1,10,TRUE)": "0.82955343384897", "T.DIST(-1,10,TRUE)": "0.17044656615103", "T.DIST(-1,10,FALSE)": "0.230361989229139", // T.DIST.2T "T.DIST.2T(1,10)": "0.34089313230206", // T.DIST.RT "T.DIST.RT(1,10)": "0.17044656615103", "T.DIST.RT(-1,10)": "0.82955343384897", // TDIST "TDIST(1,10,1)": "0.17044656615103", "TDIST(1,10,2)": "0.34089313230206", // T.INV "T.INV(0.25,10)": "-0.699812061312432", "T.INV(0.75,10)": "0.699812061312432", // T.INV.2T "T.INV.2T(1,10)": "0", "T.INV.2T(0.5,10)": "0.699812061312432", // TINV "TINV(1,10)": "0", "TINV(0.5,10)": "0.699812061312432", // TRIMMEAN "TRIMMEAN(A1:B4,10%)": "2.5", "TRIMMEAN(A1:B4,70%)": "2.5", // VAR "VAR(1,3,5,0,C1)": "4.91666666666667", "VAR(1,3,5,0,C1,TRUE)": "4", // VARA "VARA(1,3,5,0,C1)": "4.91666666666667", "VARA(1,3,5,0,C1,TRUE)": "4", // VARP "VARP(A1:A5)": "1.25", "VARP(1,3,5,0,C1,TRUE)": "3.2", // VAR.P "VAR.P(A1:A5)": "1.25", // VAR.S "VAR.S(1,3,5,0,C1)": "4.91666666666667", "VAR.S(1,3,5,0,C1,TRUE)": "4", // VARPA "VARPA(1,3,5,0,C1)": "3.6875", "VARPA(1,3,5,0,C1,TRUE)": "3.2", // WEIBULL "WEIBULL(1,3,1,FALSE)": "1.10363832351433", "WEIBULL(2,5,1.5,TRUE)": "0.985212776817482", // WEIBULL.DIST "WEIBULL.DIST(1,3,1,FALSE)": "1.10363832351433", "WEIBULL.DIST(2,5,1.5,TRUE)": "0.985212776817482", // Information Functions // ERROR.TYPE "ERROR.TYPE(1/0)": "2", "ERROR.TYPE(COT(0))": "2", "ERROR.TYPE(XOR(\"text\"))": "3", "ERROR.TYPE(HEX2BIN(2,1))": "6", "ERROR.TYPE(NA())": "7", // ISBLANK "ISBLANK(A1)": "FALSE", "ISBLANK(A5)": "TRUE", // ISERR "ISERR(A1)": "FALSE", "ISERR(NA())": "FALSE", "ISERR(POWER(0,-1)))": "TRUE", // ISERROR "ISERROR(A1)": "FALSE", "ISERROR(NA())": "TRUE", "ISERROR(\"#VALUE!\")": "FALSE", // ISEVEN "ISEVEN(A1)": "FALSE", "ISEVEN(A2)": "TRUE", "ISEVEN(G1)": "TRUE", // ISFORMULA "ISFORMULA(A1)": "FALSE", "ISFORMULA(\"A\")": "FALSE", // ISLOGICAL "ISLOGICAL(TRUE)": "TRUE", "ISLOGICAL(FALSE)": "TRUE", "ISLOGICAL(A1=A2)": "TRUE", "ISLOGICAL(\"true\")": "TRUE", "ISLOGICAL(\"false\")": "TRUE", "ISLOGICAL(A1)": "FALSE", "ISLOGICAL(20/5)": "FALSE", // ISNA "ISNA(A1)": "FALSE", "ISNA(NA())": "TRUE", // ISNONTEXT "ISNONTEXT(A1)": "TRUE", "ISNONTEXT(A5)": "TRUE", "ISNONTEXT(\"Excelize\")": "FALSE", "ISNONTEXT(NA())": "TRUE", // ISNUMBER "ISNUMBER(A1)": "TRUE", "ISNUMBER(D1)": "FALSE", "ISNUMBER(A1:B1)": "TRUE", // ISODD "ISODD(A1)": "TRUE", "ISODD(A2)": "FALSE", // ISREF "ISREF(B1)": "TRUE", "ISREF(B1:B2)": "TRUE", "ISREF(\"text\")": "FALSE", "ISREF(B1*B2)": "FALSE", // ISTEXT "ISTEXT(D1)": "TRUE", "ISTEXT(A1)": "FALSE", // N "N(10)": "10", "N(\"10\")": "10", "N(\"x\")": "0", "N(TRUE)": "1", "N(FALSE)": "0", // SHEET "SHEET()": "1", "SHEET(\"Sheet1\")": "1", // SHEETS "SHEETS()": "1", "SHEETS(A1)": "1", // TYPE "TYPE(2)": "1", "TYPE(10/2)": "1", "TYPE(C2)": "1", "TYPE(\"text\")": "2", "TYPE(TRUE)": "4", "TYPE(NA())": "16", "TYPE(MUNIT(2))": "64", // T "T(\"text\")": "text", "T(N(10))": "", // Logical Functions // AND "AND(0)": "FALSE", "AND(1)": "TRUE", "AND(1,0)": "FALSE", "AND(0,1)": "FALSE", "AND(1=1)": "TRUE", "AND(1<2)": "TRUE", "AND(1>2,2<3,2>0,3>1)": "FALSE", "AND(1=1),1=1": "TRUE", "AND(\"TRUE\",\"FALSE\")": "FALSE", // FALSE "FALSE()": "FALSE", // IFERROR "IFERROR(1/2,0)": "0.5", "IFERROR(ISERROR(),0)": "0", "IFERROR(1/0,0)": "0", "IFERROR(G1,2)": "0", "IFERROR(B2/MROUND(A2,1),0)": "2.5", // IFNA "IFNA(1,\"not found\")": "1", "IFNA(NA(),\"not found\")": "not found", "IFNA(HLOOKUP(D2,D:D,1,2),\"not found\")": "not found", // IFS "IFS(4>1,5/4,4<-1,-5/4,TRUE,0)": "1.25", "IFS(-2>1,5/-2,-2<-1,-5/-2,TRUE,0)": "2.5", "IFS(0>1,5/0,0<-1,-5/0,TRUE,0)": "0", // NOT "NOT(FALSE())": "TRUE", "NOT(\"false\")": "TRUE", "NOT(\"true\")": "FALSE", "NOT(ISBLANK(B1))": "TRUE", // OR "OR(1)": "TRUE", "OR(0)": "FALSE", "OR(1=2,2=2)": "TRUE", "OR(1=2,2=3)": "FALSE", "OR(1=1,2=3)": "TRUE", "OR(\"TRUE\",\"FALSE\")": "TRUE", "OR(A1:B1)": "TRUE", // SWITCH "SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A", "SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C", "SWITCH(4,1,\"A\",2,\"B\",3,\"C\",\"N\")": "N", // TRUE "TRUE()": "TRUE", // XOR "XOR(1>0,2>0)": "FALSE", "XOR(1>0,0>1)": "TRUE", "XOR(1>0,0>1,INT(0),INT(1),A1:A4,2)": "FALSE", // Date and Time Functions // DATE "DATE(2020,10,21)": "44125", "DATE(2020,10,21)+1": "44126", "DATE(1900,1,1)": "1", // DATEDIF "DATEDIF(43101,43101,\"D\")": "0", "DATEDIF(43101,43891,\"d\")": "790", "DATEDIF(43101,43891,\"Y\")": "2", "DATEDIF(42156,44242,\"y\")": "5", "DATEDIF(43101,43891,\"M\")": "26", "DATEDIF(42171,44242,\"m\")": "67", "DATEDIF(42156,44454,\"MD\")": "14", "DATEDIF(42171,44242,\"md\")": "30", "DATEDIF(43101,43891,\"YM\")": "2", "DATEDIF(42171,44242,\"ym\")": "7", "DATEDIF(43101,43891,\"YD\")": "59", "DATEDIF(36526,73110,\"YD\")": "60", "DATEDIF(42171,44242,\"yd\")": "244", // DATEVALUE "DATEVALUE(\"01/01/16\")": "42370", "DATEVALUE(\"01/01/2016\")": "42370", "DATEVALUE(\"01/01/29\")": "47119", "DATEVALUE(\"01/01/30\")": "10959", // DAY "DAY(0)": "0", "DAY(INT(7))": "7", "DAY(\"35\")": "4", "DAY(42171)": "16", "DAY(\"2-28-1900\")": "28", "DAY(\"31-May-2015\")": "31", "DAY(\"01/03/2019 12:14:16\")": "3", "DAY(\"January 25, 2020 01 AM\")": "25", "DAY(\"January 25, 2020 01:03 AM\")": "25", "DAY(\"January 25, 2020 12:00:00 AM\")": "25", "DAY(\"1900-1-1\")": "1", "DAY(\"12-1-1900\")": "1", "DAY(\"3-January-1900\")": "3", "DAY(\"3-February-2000\")": "3", "DAY(\"3-February-2008\")": "3", "DAY(\"01/25/20\")": "25", "DAY(\"01/25/31\")": "25", // DAYS "DAYS(2,1)": "1", "DAYS(INT(2),INT(1))": "1", "DAYS(\"02/02/2015\",\"01/01/2015\")": "32", // DAYS360 "DAYS360(\"10/10/2020\", \"10/10/2020\")": "0", "DAYS360(\"01/30/1999\", \"02/28/1999\")": "28", "DAYS360(\"01/31/1999\", \"02/28/1999\")": "28", "DAYS360(\"12/12/1999\", \"08/31/1999\")": "-101", "DAYS360(\"12/12/1999\", \"11/30/1999\")": "-12", "DAYS360(\"12/12/1999\", \"11/30/1999\",TRUE)": "-12", "DAYS360(\"01/31/1999\", \"03/31/1999\",TRUE)": "60", "DAYS360(\"01/31/1999\", \"03/31/2000\",FALSE)": "420", // EDATE "EDATE(\"01/01/2021\",-1)": "44166", "EDATE(\"01/31/2020\",1)": "43890", "EDATE(\"01/29/2020\",12)": "44225", "EDATE(\"6/12/2021\",-14)": "43933", // EOMONTH "EOMONTH(\"01/01/2021\",-1)": "44196", "EOMONTH(\"01/29/2020\",12)": "44227", "EOMONTH(\"01/12/2021\",-18)": "43677", // HOUR "HOUR(1)": "0", "HOUR(43543.5032060185)": "12", "HOUR(\"43543.5032060185\")": "12", "HOUR(\"13:00:55\")": "13", "HOUR(\"1:00 PM\")": "13", "HOUR(\"12/09/2015 08:55\")": "8", // ISOWEEKNUM "ISOWEEKNUM(42370)": "53", "ISOWEEKNUM(\"42370\")": "53", "ISOWEEKNUM(\"01/01/2005\")": "53", "ISOWEEKNUM(\"02/02/2005\")": "5", // MINUTE "MINUTE(1)": "0", "MINUTE(0.04)": "57", "MINUTE(\"0.04\")": "57", "MINUTE(\"13:35:55\")": "35", "MINUTE(\"12/09/2015 08:55\")": "55", // MONTH "MONTH(42171)": "6", "MONTH(\"31-May-2015\")": "5", // YEAR "YEAR(15)": "1900", "YEAR(\"15\")": "1900", "YEAR(2048)": "1905", "YEAR(42171)": "2015", "YEAR(\"29-May-2015\")": "2015", "YEAR(\"05/03/1984\")": "1984", // YEARFRAC "YEARFRAC(42005,42005)": "0", "YEARFRAC(42005,42094)": "0.25", "YEARFRAC(42005,42094,0)": "0.25", "YEARFRAC(42005,42094,1)": "0.243835616438356", "YEARFRAC(42005,42094,2)": "0.247222222222222", "YEARFRAC(42005,42094,3)": "0.243835616438356", "YEARFRAC(42005,42094,4)": "0.247222222222222", "YEARFRAC(\"01/01/2015\",\"03/31/2015\")": "0.25", "YEARFRAC(\"01/01/2015\",\"03/31/2015\",0)": "0.25", "YEARFRAC(\"01/01/2015\",\"03/31/2015\",1)": "0.243835616438356", "YEARFRAC(\"01/01/2015\",\"03/31/2015\",2)": "0.247222222222222", "YEARFRAC(\"01/01/2015\",\"03/31/2015\",3)": "0.243835616438356", "YEARFRAC(\"01/01/2015\",\"03/31/2015\",4)": "0.247222222222222", "YEARFRAC(\"01/01/2015\",42094)": "0.25", "YEARFRAC(42005,\"03/31/2015\",0)": "0.25", "YEARFRAC(\"01/31/2015\",\"03/31/2015\")": "0.166666666666667", "YEARFRAC(\"01/30/2015\",\"03/31/2015\")": "0.166666666666667", "YEARFRAC(\"02/29/2000\", \"02/29/2008\")": "8", "YEARFRAC(\"02/29/2000\", \"02/29/2008\",1)": "7.99817518248175", "YEARFRAC(\"02/29/2000\", \"01/29/2001\",1)": "0.915300546448087", "YEARFRAC(\"02/29/2000\", \"03/29/2000\",1)": "0.0792349726775956", "YEARFRAC(\"01/31/2000\", \"03/29/2000\",4)": "0.163888888888889", // SECOND "SECOND(\"13:35:55\")": "55", "SECOND(\"13:10:60\")": "0", "SECOND(\"13:10:61\")": "1", "SECOND(\"08:17:00\")": "0", "SECOND(\"12/09/2015 08:55\")": "0", "SECOND(\"12/09/2011 08:17:23\")": "23", "SECOND(\"43543.5032060185\")": "37", "SECOND(43543.5032060185)": "37", // TIME "TIME(5,44,32)": "0.239259259259259", "TIME(\"5\",\"44\",\"32\")": "0.239259259259259", "TIME(0,0,73)": "0.000844907407407407", // TIMEVALUE "TIMEVALUE(\"2:23\")": "0.0993055555555555", "TIMEVALUE(\"2:23 am\")": "0.0993055555555555", "TIMEVALUE(\"2:23 PM\")": "0.599305555555556", "TIMEVALUE(\"14:23:00\")": "0.599305555555556", "TIMEVALUE(\"00:02:23\")": "0.00165509259259259", "TIMEVALUE(\"01/01/2011 02:23\")": "0.0993055555555555", // WEEKDAY "WEEKDAY(0)": "7", "WEEKDAY(47119)": "2", "WEEKDAY(\"12/25/2012\")": "3", "WEEKDAY(\"12/25/2012\",1)": "3", "WEEKDAY(\"12/25/2012\",2)": "2", "WEEKDAY(\"12/25/2012\",3)": "1", "WEEKDAY(\"12/25/2012\",11)": "2", "WEEKDAY(\"12/25/2012\",12)": "1", "WEEKDAY(\"12/25/2012\",13)": "7", "WEEKDAY(\"12/25/2012\",14)": "6", "WEEKDAY(\"12/25/2012\",15)": "5", "WEEKDAY(\"12/25/2012\",16)": "4", "WEEKDAY(\"12/25/2012\",17)": "3", // WEEKNUM "WEEKNUM(\"01/01/2011\")": "1", "WEEKNUM(\"01/03/2011\")": "2", "WEEKNUM(\"01/13/2008\")": "3", "WEEKNUM(\"01/21/2008\")": "4", "WEEKNUM(\"01/30/2008\")": "5", "WEEKNUM(\"02/04/2008\")": "6", "WEEKNUM(\"01/02/2017\",2)": "2", "WEEKNUM(\"01/02/2017\",12)": "1", "WEEKNUM(\"12/31/2017\",21)": "52", "WEEKNUM(\"01/01/2017\",21)": "52", "WEEKNUM(\"01/01/2021\",21)": "53", // Text Functions // ARRAYTOTEXT "ARRAYTOTEXT(A1:D2)": "1, 4, , Month, 2, 5, , Jan", "ARRAYTOTEXT(A1:D2,0)": "1, 4, , Month, 2, 5, , Jan", "ARRAYTOTEXT(A1:D2,1)": "{1,4,,\"Month\";2,5,,\"Jan\"}", // BAHTTEXT "BAHTTEXT(-1.1)": "\u0e25\u0e1a\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e2a\u0e34\u0e1a\u0e2a\u0e15\u0e32\u0e07\u0e04\u0e4c", "BAHTTEXT(0)": "\u0e28\u0e39\u0e19\u0e22\u0e4c\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(1)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(1.1)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e2a\u0e34\u0e1a\u0e2a\u0e15\u0e32\u0e07\u0e04\u0e4c", "BAHTTEXT(2)": "\u0e2a\u0e2d\u0e07\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(3)": "\u0e2a\u0e32\u0e21\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(4)": "\u0e2a\u0e35\u0e48\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(5)": "\u0e2b\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(6)": "\u0e2b\u0e01\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(7)": "\u0e40\u0e08\u0e47\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(8)": "\u0e41\u0e1b\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(9)": "\u0e40\u0e01\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(10)": "\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(11)": "\u0e2a\u0e34\u0e1a\u0e40\u0e2d\u0e47\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(15)": "\u0e2a\u0e34\u0e1a\u0e2b\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(20)": "\u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(100)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e23\u0e49\u0e2d\u0e22\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(130)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e23\u0e49\u0e2d\u0e22\u0e2a\u0e32\u0e21\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(1000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1e\u0e31\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(10000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e2b\u0e21\u0e37\u0e48\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(100000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e41\u0e2a\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", "BAHTTEXT(1000000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e25\u0e49\u0e32\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19", // CHAR "CHAR(65)": "A", "CHAR(97)": "a", "CHAR(63)": "?", "CHAR(51)": "3", // CLEAN "CLEAN(\"\u0009clean text\")": "clean text", "CLEAN(0)": "0", // CODE "CODE(\"Alpha\")": "65", "CODE(\"alpha\")": "97", "CODE(\"?\")": "63", "CODE(\"3\")": "51", "CODE(\"\")": "0", // CONCAT "CONCAT(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02", "CONCAT(MUNIT(2))": "1001", "CONCAT(A1:B2)": "1425", // CONCATENATE "CONCATENATE(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02", "CONCATENATE(MUNIT(2))": "1001", "CONCATENATE(A1:B2)": "1425", // DBCS "DBCS(\"\")": "", "DBCS(123.456)": "123.456", "DBCS(\"123.456\")": "123.456", // EXACT "EXACT(1,\"1\")": "TRUE", "EXACT(1,1)": "TRUE", "EXACT(\"A\",\"a\")": "FALSE", // FIXED "FIXED(5123.591)": "5,123.591", "FIXED(5123.591,1)": "5,123.6", "FIXED(5123.591,0)": "5,124", "FIXED(5123.591,-1)": "5,120", "FIXED(5123.591,-2)": "5,100", "FIXED(5123.591,-3,TRUE)": "5000", "FIXED(5123.591,-5)": "0", "FIXED(-77262.23973,-5)": "-100,000", // FIND "FIND(\"T\",\"Original Text\")": "10", "FIND(\"t\",\"Original Text\")": "13", "FIND(\"i\",\"Original Text\")": "3", "FIND(\"i\",\"Original Text\",4)": "5", "FIND(\"\",\"Original Text\")": "1", "FIND(\"\",\"Original Text\",2)": "2", "FIND(\"s\",\"Sales\",2)": "5", "FIND(D1:E2,\"Month\")": "1", // FINDB "FINDB(\"T\",\"Original Text\")": "10", "FINDB(\"t\",\"Original Text\")": "13", "FINDB(\"i\",\"Original Text\")": "3", "FINDB(\"i\",\"Original Text\",4)": "5", "FINDB(\"\",\"Original Text\")": "1", "FINDB(\"\",\"Original Text\",2)": "2", "FINDB(\"s\",\"Sales\",2)": "5", // LEFT "LEFT(\"Original Text\")": "O", "LEFT(\"Original Text\",4)": "Orig", "LEFT(\"Original Text\",0)": "", "LEFT(\"Original Text\",13)": "Original Text", "LEFT(\"Original Text\",20)": "Original Text", "LEFT(\"オリジナルテキスト\")": "オ", "LEFT(\"オリジナルテキスト\",2)": "オリ", "LEFT(\"オリジナルテキスト\",5)": "オリジナル", "LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ", "LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // LEFTB "LEFTB(\"Original Text\")": "O", "LEFTB(\"Original Text\",4)": "Orig", "LEFTB(\"Original Text\",0)": "", "LEFTB(\"Original Text\",13)": "Original Text", "LEFTB(\"Original Text\",20)": "Original Text", // LEN "LEN(\"\")": "0", "LEN(D1)": "5", "LEN(\"テキスト\")": "4", "LEN(\"オリジナルテキスト\")": "9", "LEN(7+LEN(A1&B1&C1))": "1", "LEN(8+LEN(A1+(C1-B1)))": "2", // LENB "LENB(\"\")": "0", "LENB(D1)": "5", "LENB(\"テキスト\")": "8", "LENB(\"オリジナルテキスト\")": "18", // LOWER "LOWER(\"test\")": "test", "LOWER(\"TEST\")": "test", "LOWER(\"Test\")": "test", "LOWER(\"TEST 123\")": "test 123", // MID "MID(\"Original Text\",7,1)": "a", "MID(\"Original Text\",4,7)": "ginal T", "MID(\"255 years\",3,1)": "5", "MID(\"text\",3,6)": "xt", "MID(\"text\",6,0)": "", "MID(\"你好World\",5,1)": "r", "MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8", "MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD", // MIDB "MIDB(\"Original Text\",7,1)": "a", "MIDB(\"Original Text\",4,7)": "ginal T", "MIDB(\"255 years\",3,1)": "5", "MIDB(\"text\",3,6)": "xt", "MIDB(\"text\",6,0)": "", "MIDB(\"你好World\",5,1)": "W", "MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA", "MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3", // PROPER "PROPER(\"this is a test sentence\")": "This Is A Test Sentence", "PROPER(\"THIS IS A TEST SENTENCE\")": "This Is A Test Sentence", "PROPER(\"123tEST teXT\")": "123Test Text", "PROPER(\"Mr. SMITH's address\")": "Mr. Smith'S Address", // REPLACE "REPLACE(\"test string\",7,3,\"X\")": "test sXng", "REPLACE(\"second test string\",8,4,\"XXX\")": "second XXX string", "REPLACE(\"text\",5,0,\" and char\")": "text and char", "REPLACE(\"text\",1,20,\"char and \")": "char and ", // REPLACEB "REPLACEB(\"test string\",7,3,\"X\")": "test sXng", "REPLACEB(\"second test string\",8,4,\"XXX\")": "second XXX string", "REPLACEB(\"text\",5,0,\" and char\")": "text and char", "REPLACEB(\"text\",1,20,\"char and \")": "char and ", // REPT "REPT(\"*\",0)": "", "REPT(\"*\",1)": "*", "REPT(\"**\",2)": "****", // RIGHT "RIGHT(\"Original Text\")": "t", "RIGHT(\"Original Text\",4)": "Text", "RIGHT(\"Original Text\",0)": "", "RIGHT(\"Original Text\",13)": "Original Text", "RIGHT(\"Original Text\",20)": "Original Text", "RIGHT(\"オリジナルテキスト\")": "ト", "RIGHT(\"オリジナルテキスト\",2)": "スト", "RIGHT(\"オリジナルテキスト\",4)": "テキスト", "RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト", "RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // RIGHTB "RIGHTB(\"Original Text\")": "t", "RIGHTB(\"Original Text\",4)": "Text", "RIGHTB(\"Original Text\",0)": "", "RIGHTB(\"Original Text\",13)": "Original Text", "RIGHTB(\"Original Text\",20)": "Original Text", // SUBSTITUTE "SUBSTITUTE(\"abab\",\"a\",\"X\")": "XbXb", "SUBSTITUTE(\"abab\",\"a\",\"X\",2)": "abXb", "SUBSTITUTE(\"abab\",\"x\",\"X\",2)": "abab", "SUBSTITUTE(\"John is 5 years old\",\"John\",\"Jack\")": "Jack is 5 years old", "SUBSTITUTE(\"John is 5 years old\",\"5\",\"6\")": "John is 6 years old", // TEXT "TEXT(\"07/07/2015\",\"mm/dd/yyyy\")": "07/07/2015", "TEXT(42192,\"mm/dd/yyyy\")": "07/07/2015", "TEXT(42192,\"mmm dd yyyy\")": "Jul 07 2015", "TEXT(0.75,\"hh:mm\")": "18:00", "TEXT(36.363636,\"0.00\")": "36.36", "TEXT(567.9,\"$#,##0.00\")": "$567.90", "TEXT(-5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "- $5.00", "TEXT(5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "+ $5.00", // TEXTAFTER "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\")": "'s, red hood", "TEXTAFTER(\"Red riding hood's, red hood\",\"HOOD\",1,1)": "'s, red hood", "TEXTAFTER(\"Red riding hood's, red hood\",\"basket\",1,0,0,\"x\")": "x", "TEXTAFTER(\"Red riding hood's, red hood\",\"basket\",1,0,1,\"x\")": "", "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",-1)": "", "TEXTAFTER(\"Jones,Bob\",\",\")": "Bob", "TEXTAFTER(\"12 ft x 20 ft\",\" x \")": "20 ft", "TEXTAFTER(\"ABX-112-Red-Y\",\"-\",1)": "112-Red-Y", "TEXTAFTER(\"ABX-112-Red-Y\",\"-\",2)": "Red-Y", "TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-1)": "Y", "TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-2)": "Red-Y", "TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-3)": "112-Red-Y", "TEXTAFTER(\"ABX-123-Red-XYZ\",\"-\",-4,0,1)": "ABX-123-Red-XYZ", "TEXTAFTER(\"ABX-123-Red-XYZ\",\"A\")": "BX-123-Red-XYZ", // TEXTBEFORE "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\")": "Red riding ", "TEXTBEFORE(\"Red riding hood's, red hood\",\"HOOD\",1,1)": "Red riding ", "TEXTBEFORE(\"Red riding hood's, red hood\",\"basket\",1,0,0,\"x\")": "x", "TEXTBEFORE(\"Red riding hood's, red hood\",\"basket\",1,0,1,\"x\")": "Red riding hood's, red hood", "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",-1)": "Red riding hood's, red ", "TEXTBEFORE(\"Jones,Bob\",\",\")": "Jones", "TEXTBEFORE(\"12 ft x 20 ft\",\" x \")": "12 ft", "TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",1)": "ABX", "TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",2)": "ABX-112", "TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",-1)": "ABX-112-Red", "TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",-2)": "ABX-112", "TEXTBEFORE(\"ABX-123-Red-XYZ\",\"-\",4,0,1)": "ABX-123-Red-XYZ", "TEXTBEFORE(\"ABX-112-Red-Y\",\"A\")": "", // TEXTJOIN "TEXTJOIN(\"-\",TRUE,1,2,3,4)": "1-2-3-4", "TEXTJOIN(A4,TRUE,A1:B2)": "1040205", "TEXTJOIN(\",\",FALSE,A1:C2)": "1,4,,2,5,", "TEXTJOIN(\",\",TRUE,A1:C2)": "1,4,2,5", "TEXTJOIN(\",\",TRUE,MUNIT(2))": "1,0,0,1", // TRIM "TRIM(\" trim text \")": "trim text", "TRIM(0)": "0", // UNICHAR "UNICHAR(65)": "A", "UNICHAR(97)": "a", "UNICHAR(63)": "?", "UNICHAR(51)": "3", // UNICODE "UNICODE(\"Alpha\")": "65", "UNICODE(\"alpha\")": "97", "UNICODE(\"?\")": "63", "UNICODE(\"3\")": "51", // UNIQUE "TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9))": "Jan,Feb", "TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, FALSE))": "Jan,Feb", "TEXTJOIN(\",\", TRUE, UNIQUE(E2:E9, FALSE, FALSE))": "North 1,North 2,South 1,South 2", "TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, TRUE))": "", // UPPER "UPPER(\"test\")": "TEST", "UPPER(\"TEST\")": "TEST", "UPPER(\"Test\")": "TEST", "UPPER(\"TEST 123\")": "TEST 123", // VALUE "VALUE(\"50\")": "50", "VALUE(\"1.0E-07\")": "0.0000001", "VALUE(\"5,000\")": "5000", "VALUE(\"20%\")": "0.2", "VALUE(\"12:00:00\")": "0.5", "VALUE(\"01/02/2006 15:04:05\")": "38719.6278356481", // VALUETOTEXT "VALUETOTEXT(A1)": "1", "VALUETOTEXT(A1,0)": "1", "VALUETOTEXT(A1,1)": "1", "VALUETOTEXT(D1)": "Month", "VALUETOTEXT(D1,0)": "Month", "VALUETOTEXT(D1,1)": "\"Month\"", // Conditional Functions // IF "IF(1=1)": "TRUE", "IF(1<>1)": "FALSE", "IF(5<0, \"negative\", \"positive\")": "positive", "IF(-2<0, \"negative\", \"positive\")": "negative", "IF(1=1, \"equal\", \"notequal\")": "equal", "IF(1<>1, \"equal\", \"notequal\")": "notequal", "IF(\"A\"=\"A\", \"equal\", \"notequal\")": "equal", "IF(\"A\"<>\"A\", \"equal\", \"notequal\")": "notequal", "IF(FALSE,0,ROUND(4/2,0))": "2", "IF(TRUE,ROUND(4/2,0),0)": "2", "IF(A4>0.4,\"TRUE\",\"FALSE\")": "FALSE", // Excel Lookup and Reference Functions // ADDRESS "ADDRESS(1,1,1,TRUE)": "$A$1", "ADDRESS(1,2,1,TRUE)": "$B$1", "ADDRESS(1,1,1,FALSE)": "R1C1", "ADDRESS(1,2,1,FALSE)": "R1C2", "ADDRESS(1,1,2,TRUE)": "A$1", "ADDRESS(1,2,2,TRUE)": "B$1", "ADDRESS(1,1,2,FALSE)": "R1C[1]", "ADDRESS(1,2,2,FALSE)": "R1C[2]", "ADDRESS(1,1,3,TRUE)": "$A1", "ADDRESS(1,2,3,TRUE)": "$B1", "ADDRESS(1,1,3,FALSE)": "R[1]C1", "ADDRESS(1,2,3,FALSE)": "R[1]C2", "ADDRESS(1,1,4,TRUE)": "A1", "ADDRESS(1,2,4,TRUE)": "B1", "ADDRESS(1,1,4,FALSE)": "R[1]C[1]", "ADDRESS(1,2,4,FALSE)": "R[1]C[2]", "ADDRESS(1,1,4,TRUE,\"\")": "!A1", "ADDRESS(1,2,4,TRUE,\"\")": "!B1", "ADDRESS(1,1,4,TRUE,\"Sheet1\")": "Sheet1!A1", // CHOOSE "CHOOSE(4,\"red\",\"blue\",\"green\",\"brown\")": "brown", "CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red", "SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))": "9", // COLUMN "COLUMN()": "3", "COLUMN(Sheet1!A1)": "1", "COLUMN(Sheet1!A1:B1:C1)": "1", "COLUMN(Sheet1!F1:G1)": "6", "COLUMN(H1)": "8", // COLUMNS "COLUMNS(B1)": "1", "COLUMNS(1:1)": "16384", "COLUMNS(Sheet1!1:1)": "16384", "COLUMNS(B1:E5)": "4", "COLUMNS(Sheet1!E5:H7:B1)": "7", "COLUMNS(E5:H7:B1:C1:Z1:C1:B1)": "25", "COLUMNS(E5:B1)": "4", "COLUMNS(EM38:HZ81)": "92", // HLOOKUP "HLOOKUP(D2,D2:D8,1,FALSE)": "Jan", "HLOOKUP(F3,F3:F8,3,FALSE)": "34440", "HLOOKUP(INT(F3),F3:F8,3,FALSE)": "34440", "HLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1", // HYPERLINK "HYPERLINK(\"https://github.com/xuri/excelize\")": "https://github.com/xuri/excelize", "HYPERLINK(\"https://github.com/xuri/excelize\",\"Excelize\")": "Excelize", // VLOOKUP "VLOOKUP(D2,D:D,1,FALSE)": "Jan", "VLOOKUP(D2,D1:D10,1)": "Jan", "VLOOKUP(D2,D1:D11,1)": "Feb", "VLOOKUP(D2,D1:D10,1,FALSE)": "Jan", "VLOOKUP(INT(36693),F2:F2,1,FALSE)": "36693", "VLOOKUP(INT(F2),F3:F9,1)": "32080", "VLOOKUP(INT(F2),F3:F9,1,TRUE)": "32080", "VLOOKUP(MUNIT(3),MUNIT(3),1)": "0", "VLOOKUP(A1,A3:B5,1)": "0", "VLOOKUP(A1:A2,A1:A1,1)": "1", "VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1", // INDEX "INDEX(0,0,0)": "0", "INDEX(A1,0,0)": "1", "INDEX(A1:A1,0,0)": "1", "SUM(INDEX(A1:B1,1))": "5", "SUM(INDEX(A1:B1,1,0))": "5", "SUM(INDEX(A1:B2,2,0))": "7", "SUM(INDEX(A1:B4,0,2))": "9", "SUM(INDEX(E1:F5,5,2))": "34440", // INDIRECT "INDIRECT(\"E1\")": "Team", "INDIRECT(\"E\"&1)": "Team", "INDIRECT(\"E\"&ROW())": "Team", "INDIRECT(\"E\"&ROW(),TRUE)": "Team", "INDIRECT(\"R1C5\",FALSE)": "Team", "INDIRECT(\"R\"&1&\"C\"&5,FALSE)": "Team", "SUM(INDIRECT(\"A1:B2\"))": "12", "SUM(INDIRECT(\"A1:B2\",TRUE))": "12", "SUM(INDIRECT(\"R1C1:R2C2\",FALSE))": "12", // LOOKUP "LOOKUP(F8,F8:F9,F8:F9)": "32080", "LOOKUP(F8,F8:F9,D8:D9)": "Feb", "LOOKUP(E3,E2:E5,F2:F5)": "22100", "LOOKUP(E3,E2:F5)": "22100", "LOOKUP(F3+1,F3:F4,F3:F4)": "22100", "LOOKUP(F4+1,F3:F4,F3:F4)": "53321", "LOOKUP(1,MUNIT(1))": "1", "LOOKUP(1,MUNIT(1),MUNIT(1))": "1", // ROW "ROW()": "1", "ROW(Sheet1!A1)": "1", "ROW(Sheet1!A1:B2:C3)": "1", "ROW(Sheet1!F5:G6)": "5", "ROW(A8)": "8", // ROWS "ROWS(B1)": "1", "ROWS(B:B)": "1048576", "ROWS(Sheet1!B:B)": "1048576", "ROWS(B1:E5)": "5", "ROWS(Sheet1!E5:H7:B1)": "7", "ROWS(E5:H8:B2:C3:Z26:C3:B2)": "25", "ROWS(E5:B1)": "5", "ROWS(EM38:HZ81)": "44", // Web Functions // ENCODEURL "ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As", // Financial Functions // ACCRINT "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,0,TRUE)": "1600", "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,0,FALSE)": "1600", // ACCRINTM "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000)": "800", "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,3)": "800", // AMORDEGRC "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%)": "42", "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,4)": "42", "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,40%,4)": "42", "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,25%,4)": "41", "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",109,1,25%,4)": "54", "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",110,2,25%,4)": "0", // AMORLINC "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,4)": "30", "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,0%,4)": "0", "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,20,15%,4)": "0", "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,6,15%,4)": "0.6875", "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,0,15%,4)": "16.8125", // COUPDAYBS "COUPDAYBS(\"02/24/2000\",\"11/24/2000\",4,4)": "0", "COUPDAYBS(\"03/27/2000\",\"11/29/2000\",4,4)": "28", "COUPDAYBS(\"02/29/2000\",\"04/01/2000\",4,4)": "58", "COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4)": "66", "COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4,1)": "68", "COUPDAYBS(\"10/31/2011\",\"02/26/2012\",4,0)": "65", // COUPDAYS "COUPDAYS(\"01/01/2011\",\"10/25/2012\",4)": "90", "COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,1)": "92", // COUPDAYSNC "COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4)": "24", "COUPDAYSNC(\"04/01/2012\",\"03/31/2020\",2)": "179", // COUPNCD "COUPNCD(\"01/01/2011\",\"10/25/2012\",4)": "40568", "COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40568", "COUPNCD(\"10/25/2011\",\"01/01/2012\",4)": "40909", "COUPNCD(\"04/01/2012\",\"03/31/2020\",2)": "41182", "COUPNCD(\"01/01/2000\",\"08/30/2001\",2)": "36585", // COUPNUM "COUPNUM(\"01/01/2011\",\"10/25/2012\",4)": "8", "COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0)": "8", "COUPNUM(\"09/30/2017\",\"03/31/2021\",4,0)": "14", // COUPPCD "COUPPCD(\"01/01/2011\",\"10/25/2012\",4)": "40476", "COUPPCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40476", "COUPPCD(\"10/25/2011\",\"01/01/2012\",4)": "40817", // CUMIPMT "CUMIPMT(0.05/12,60,50000,1,12,0)": "-2294.97753732664", "CUMIPMT(0.05/12,60,50000,13,24,0)": "-1833.10006657389", // CUMPRINC "CUMPRINC(0.05/12,60,50000,1,12,0)": "-9027.76264907988", "CUMPRINC(0.05/12,60,50000,13,24,0)": "-9489.64011983263", // DB "DB(0,1000,5,1)": "0", "DB(10000,1000,5,1)": "3690", "DB(10000,1000,5,2)": "2328.39", "DB(10000,1000,5,1,6)": "1845", "DB(10000,1000,5,6,6)": "238.527124587882", // DDB "DDB(0,1000,5,1)": "0", "DDB(10000,1000,5,1)": "4000", "DDB(10000,1000,5,2)": "2400", "DDB(10000,1000,5,3)": "1440", "DDB(10000,1000,5,4)": "864", "DDB(10000,1000,5,5)": "296", // DISC "DISC(\"04/01/2016\",\"03/31/2021\",95,100)": "0.01", // DOLLAR "DOLLAR(1234.56)": "$1,234.56", "DOLLAR(1234.56,0)": "$1,235", "DOLLAR(1234.56,1)": "$1,234.6", "DOLLAR(1234.56,2)": "$1,234.56", "DOLLAR(1234.56,3)": "$1,234.560", "DOLLAR(1234.56,-2)": "$1,200", "DOLLAR(1234.56,-3)": "$1,000", "DOLLAR(-1234.56,3)": "($1,234.560)", "DOLLAR(-1234.56,-3)": "($1,000)", // DOLLARDE "DOLLARDE(1.01,16)": "1.0625", // DOLLARFR "DOLLARFR(1.0625,16)": "1.01", // DURATION "DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4)": "6.67442279848313", // EFFECT "EFFECT(0.1,4)": "0.103812890625", "EFFECT(0.025,2)": "0.02515625", // EUROCONVERT "EUROCONVERT(1.47,\"EUR\",\"EUR\")": "1.47", "EUROCONVERT(1.47,\"EUR\",\"DEM\")": "2.88", "EUROCONVERT(1.47,\"FRF\",\"DEM\")": "0.44", "EUROCONVERT(1.47,\"FRF\",\"DEM\",FALSE)": "0.44", "EUROCONVERT(1.47,\"FRF\",\"DEM\",FALSE,3)": "0.44", "EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,3)": "0.43810592", // FV "FV(0.05/12,60,-1000)": "68006.0828408434", "FV(0.1/4,16,-2000,0,1)": "39729.4608941662", "FV(0,16,-2000)": "32000", // FVSCHEDULE "FVSCHEDULE(10000,A1:A5)": "240000", "FVSCHEDULE(10000,0.5)": "15000", // INTRATE "INTRATE(\"04/01/2005\",\"03/31/2010\",1000,2125)": "0.225", // IPMT "IPMT(0.05/12,2,60,50000)": "-205.26988187972", "IPMT(0.035/4,2,8,0,5000,1)": "5.25745523782908", // ISPMT "ISPMT(0.05/12,1,60,50000)": "-204.861111111111", "ISPMT(0.05/12,2,60,50000)": "-201.388888888889", "ISPMT(0.05/12,2,1,50000)": "208.333333333333", // MDURATION "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4)": "6.54355176321876", // NOMINAL "NOMINAL(0.025,12)": "0.0247180352381129", // NPER "NPER(0.04,-6000,50000)": "10.3380350715077", "NPER(0,-6000,50000)": "8.33333333333333", "NPER(0.06/4,-2000,60000,30000,1)": "52.7947737092748", // NPV "NPV(0.02,-5000,\"\",800)": "-4133.02575932334", // ODDFPRICE "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": "107.691830256629", "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,1)": "106.766915010929", "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,3)": "106.7819138147", "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,4)": "106.771913772467", "ODDFPRICE(\"11/11/2008\",\"03/01/2021\",\"10/15/2008\",\"03/01/2009\",7.85%,6.25%,100,2,1)": "113.597717474079", "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"09/30/2017\",5.5%,3.5%,100,4,0)": "106.72930611878", "ODDFPRICE(\"11/11/2008\",\"03/29/2021\",\"08/15/2008\",\"03/29/2009\",0.0785,0.0625,100,2,1)": "113.61826640814", // ODDFYIELD "ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,1)": "0.0495998049937776", "ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,2)": "0.0496289417392839", "ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,4,1)": "0.0464750282973541", // ODDLPRICE "ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "5.0517841252892", "ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "10.3667274303228", // ODDLYIELD "ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "0.0451922356291692", "ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "0.0882287538349037", // PDURATION "PDURATION(0.04,10000,15000)": "10.3380350715076", // PMT "PMT(0,8,0,5000,1)": "-625", "PMT(0.035/4,8,0,5000,1)": "-600.852027180466", // PRICE "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2)": "110.655105178443", "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,4)": "110.655105178443", "PRICE(\"04/01/2012\",\"03/31/2020\",12%,10%,100,2)": "110.834483593216", "PRICE(\"01/01/2010\",\"06/30/2010\",0.5,1,1,1,4)": "8.92419088847661", // PPMT "PPMT(0.05/12,2,60,50000)": "-738.291800320824", "PPMT(0.035/4,2,8,0,5000,1)": "-606.109482418295", // PRICEDISC "PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100)": "90", "PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100,3)": "90", "PRICEDISC(\"42826\",\"03/31/2021\",2.5%,100,3)": "90", // PRICEMAT "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": "107.170454545455", "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,0)": "107.170454545455", // PV "PV(0,60,1000)": "-60000", "PV(5%/12,60,1000)": "-52990.7063239275", "PV(10%/4,16,2000,0,1)": "-26762.7554528811", // RATE "RATE(60,-1000,50000)": "0.0061834131621292", "RATE(24,-800,0,20000,1)": "0.00325084350160374", "RATE(48,-200,8000,3,1,0.5)": "0.0080412665831637", // RECEIVED "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%)": "1290.32258064516", "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,0)": "1290.32258064516", // RRI "RRI(10,10000,15000)": "0.0413797439924106", // SLN "SLN(10000,1000,5)": "1800", // SYD "SYD(10000,1000,5,1)": "3000", "SYD(10000,1000,5,2)": "2400", // TBILLEQ "TBILLEQ(\"01/01/2017\",\"06/30/2017\",2.5%)": "0.0256680731364276", // TBILLPRICE "TBILLPRICE(\"02/01/2017\",\"06/30/2017\",2.75%)": "98.8618055555556", // TBILLYIELD "TBILLYIELD(\"02/01/2017\",\"06/30/2017\",99)": "0.024405125076266", // VDB "VDB(10000,1000,5,0,1)": "4000", "VDB(10000,1000,5,1,3)": "3840", "VDB(10000,1000,5,3,5)": "1160", "VDB(10000,1000,5,3.5,5,2)": "709.6", "VDB(10000,1000,5,3,5,0.2,FALSE)": "3600", "VDB(10000,1000,5,3,5,0.2,TRUE)": "693.633024", "VDB(24000,3000,10,0,0.875,2)": "4200", "VDB(24000,3000,10,0.1,1)": "4233.6", "VDB(24000,3000,10,0.1,1,1)": "2138.4", "VDB(24000,3000,100,50,100,1)": "10377.2944184652", "VDB(24000,3000,100,50,100,2)": "5740.0723220908", // YIELD "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4)": "0.0975631546829798", "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,4)": "0.0976269355643988", "YIELD(\"01/01/2010\",\"06/30/2010\",0.5,1,1,1,4)": "1.91285866099894", "YIELD(\"01/01/2010\",\"06/30/2010\",0,1,1,1,4)": "0", "YIELD(\"01/01/2010\",\"01/02/2020\",100,68.15518653988686,1,1,1)": "64", // YIELDDISC "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100)": "0.0622012325059031", "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,0)": "0.0622012325059031", // YIELDMAT "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101)": "0.0419422478838651", "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "0.0419422478838651", // DISPIMG "_xlfn.DISPIMG(\"ID_********************************\",1)": "ID_********************************", } for formula, expected := range mathCalc { f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } mathCalcError := map[string][]string{ "1/0": {"", "#DIV/0!"}, "1^\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "\"text\"^1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "1+\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "\"text\"+1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "1-\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "\"text\"-1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "1*\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "\"text\"*1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "1/\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, "\"text\"/1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"}, // Engineering Functions // BESSELI "BESSELI()": {"#VALUE!", "BESSELI requires 2 numeric arguments"}, "BESSELI(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELI(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // BESSELJ "BESSELJ()": {"#VALUE!", "BESSELJ requires 2 numeric arguments"}, "BESSELJ(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELJ(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // BESSELK "BESSELK()": {"#VALUE!", "BESSELK requires 2 numeric arguments"}, "BESSELK(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELK(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELK(-1,0)": {"#NUM!", "#NUM!"}, "BESSELK(1,-1)": {"#NUM!", "#NUM!"}, // BESSELY "BESSELY()": {"#VALUE!", "BESSELY requires 2 numeric arguments"}, "BESSELY(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELY(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BESSELY(-1,0)": {"#NUM!", "#NUM!"}, "BESSELY(1,-1)": {"#NUM!", "#NUM!"}, // BIN2DEC "BIN2DEC()": {"#VALUE!", "BIN2DEC requires 1 numeric argument"}, "BIN2DEC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // BIN2HEX "BIN2HEX()": {"#VALUE!", "BIN2HEX requires at least 1 argument"}, "BIN2HEX(1,1,1)": {"#VALUE!", "BIN2HEX allows at most 2 arguments"}, "BIN2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BIN2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BIN2HEX(12345678901,10)": {"#NUM!", "#NUM!"}, "BIN2HEX(1,-1)": {"#NUM!", "#NUM!"}, "BIN2HEX(31,1)": {"#NUM!", "#NUM!"}, // BIN2OCT "BIN2OCT()": {"#VALUE!", "BIN2OCT requires at least 1 argument"}, "BIN2OCT(1,1,1)": {"#VALUE!", "BIN2OCT allows at most 2 arguments"}, "BIN2OCT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BIN2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BIN2OCT(-12345678901 ,10)": {"#NUM!", "#NUM!"}, "BIN2OCT(1,-1)": {"#NUM!", "#NUM!"}, "BIN2OCT(8,1)": {"#NUM!", "#NUM!"}, // BITAND "BITAND()": {"#VALUE!", "BITAND requires 2 numeric arguments"}, "BITAND(-1,2)": {"#NUM!", "#NUM!"}, "BITAND(2^48,2)": {"#NUM!", "#NUM!"}, "BITAND(1,-1)": {"#NUM!", "#NUM!"}, "BITAND(\"\",-1)": {"#NUM!", "#NUM!"}, "BITAND(1,\"\")": {"#NUM!", "#NUM!"}, "BITAND(1,2^48)": {"#NUM!", "#NUM!"}, // BITLSHIFT "BITLSHIFT()": {"#VALUE!", "BITLSHIFT requires 2 numeric arguments"}, "BITLSHIFT(-1,2)": {"#NUM!", "#NUM!"}, "BITLSHIFT(2^48,2)": {"#NUM!", "#NUM!"}, "BITLSHIFT(1,-1)": {"#NUM!", "#NUM!"}, "BITLSHIFT(\"\",-1)": {"#NUM!", "#NUM!"}, "BITLSHIFT(1,\"\")": {"#NUM!", "#NUM!"}, "BITLSHIFT(1,2^48)": {"#NUM!", "#NUM!"}, // BITOR "BITOR()": {"#VALUE!", "BITOR requires 2 numeric arguments"}, "BITOR(-1,2)": {"#NUM!", "#NUM!"}, "BITOR(2^48,2)": {"#NUM!", "#NUM!"}, "BITOR(1,-1)": {"#NUM!", "#NUM!"}, "BITOR(\"\",-1)": {"#NUM!", "#NUM!"}, "BITOR(1,\"\")": {"#NUM!", "#NUM!"}, "BITOR(1,2^48)": {"#NUM!", "#NUM!"}, // BITRSHIFT "BITRSHIFT()": {"#VALUE!", "BITRSHIFT requires 2 numeric arguments"}, "BITRSHIFT(-1,2)": {"#NUM!", "#NUM!"}, "BITRSHIFT(2^48,2)": {"#NUM!", "#NUM!"}, "BITRSHIFT(1,-1)": {"#NUM!", "#NUM!"}, "BITRSHIFT(\"\",-1)": {"#NUM!", "#NUM!"}, "BITRSHIFT(1,\"\")": {"#NUM!", "#NUM!"}, "BITRSHIFT(1,2^48)": {"#NUM!", "#NUM!"}, // BITXOR "BITXOR()": {"#VALUE!", "BITXOR requires 2 numeric arguments"}, "BITXOR(-1,2)": {"#NUM!", "#NUM!"}, "BITXOR(2^48,2)": {"#NUM!", "#NUM!"}, "BITXOR(1,-1)": {"#NUM!", "#NUM!"}, "BITXOR(\"\",-1)": {"#NUM!", "#NUM!"}, "BITXOR(1,\"\")": {"#NUM!", "#NUM!"}, "BITXOR(1,2^48)": {"#NUM!", "#NUM!"}, // COMPLEX "COMPLEX()": {"#VALUE!", "COMPLEX requires at least 2 arguments"}, "COMPLEX(10,-5,\"\")": {"#VALUE!", "#VALUE!"}, "COMPLEX(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "COMPLEX(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "COMPLEX(10,-5,\"i\",0)": {"#VALUE!", "COMPLEX allows at most 3 arguments"}, // CONVERT "CONVERT()": {"#VALUE!", "CONVERT requires 3 arguments"}, "CONVERT(\"\",\"m\",\"yd\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONVERT(20.2,\"m\",\"C\")": {"#N/A", "#N/A"}, "CONVERT(20.2,\"\",\"C\")": {"#N/A", "#N/A"}, "CONVERT(100,\"dapt\",\"pt\")": {"#N/A", "#N/A"}, "CONVERT(1,\"ft\",\"day\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"kpt\",\"lt\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"lt\",\"kpt\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"kiqt\",\"pt\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"pt\",\"kiqt\")": {"#N/A", "#N/A"}, "CONVERT(12345.6,\"baton\",\"cwt\")": {"#N/A", "#N/A"}, "CONVERT(12345.6,\"cwt\",\"baton\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"xxxx\",\"m\")": {"#N/A", "#N/A"}, "CONVERT(234.56,\"m\",\"xxxx\")": {"#N/A", "#N/A"}, // DEC2BIN "DEC2BIN()": {"#VALUE!", "DEC2BIN requires at least 1 argument"}, "DEC2BIN(1,1,1)": {"#VALUE!", "DEC2BIN allows at most 2 arguments"}, "DEC2BIN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2BIN(-513,10)": {"#NUM!", "#NUM!"}, "DEC2BIN(1,-1)": {"#NUM!", "#NUM!"}, "DEC2BIN(2,1)": {"#NUM!", "#NUM!"}, // DEC2HEX "DEC2HEX()": {"#VALUE!", "DEC2HEX requires at least 1 argument"}, "DEC2HEX(1,1,1)": {"#VALUE!", "DEC2HEX allows at most 2 arguments"}, "DEC2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2HEX(-549755813888,10)": {"#NUM!", "#NUM!"}, "DEC2HEX(1,-1)": {"#NUM!", "#NUM!"}, "DEC2HEX(31,1)": {"#NUM!", "#NUM!"}, // DEC2OCT "DEC2OCT()": {"#VALUE!", "DEC2OCT requires at least 1 argument"}, "DEC2OCT(1,1,1)": {"#VALUE!", "DEC2OCT allows at most 2 arguments"}, "DEC2OCT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DEC2OCT(-536870912 ,10)": {"#NUM!", "#NUM!"}, "DEC2OCT(1,-1)": {"#NUM!", "#NUM!"}, "DEC2OCT(8,1)": {"#NUM!", "#NUM!"}, // DELTA "DELTA()": {"#VALUE!", "DELTA requires at least 1 argument"}, "DELTA(0,0,0)": {"#VALUE!", "DELTA allows at most 2 arguments"}, "DELTA(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DELTA(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ERF "ERF()": {"#VALUE!", "ERF requires at least 1 argument"}, "ERF(0,0,0)": {"#VALUE!", "ERF allows at most 2 arguments"}, "ERF(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ERF(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ERF.PRECISE "ERF.PRECISE()": {"#VALUE!", "ERF.PRECISE requires 1 argument"}, "ERF.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ERFC "ERFC()": {"#VALUE!", "ERFC requires 1 argument"}, "ERFC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ERFC.PRECISE "ERFC.PRECISE()": {"#VALUE!", "ERFC.PRECISE requires 1 argument"}, "ERFC.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // GESTEP "GESTEP()": {"#VALUE!", "GESTEP requires at least 1 argument"}, "GESTEP(0,0,0)": {"#VALUE!", "GESTEP allows at most 2 arguments"}, "GESTEP(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GESTEP(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // HEX2BIN "HEX2BIN()": {"#VALUE!", "HEX2BIN requires at least 1 argument"}, "HEX2BIN(1,1,1)": {"#VALUE!", "HEX2BIN allows at most 2 arguments"}, "HEX2BIN(\"X\",1)": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"}, "HEX2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HEX2BIN(-513,10)": {"#NUM!", "strconv.ParseInt: parsing \"-\": invalid syntax"}, "HEX2BIN(1,-1)": {"#NUM!", "#NUM!"}, "HEX2BIN(2,1)": {"#NUM!", "#NUM!"}, // HEX2DEC "HEX2DEC()": {"#VALUE!", "HEX2DEC requires 1 numeric argument"}, "HEX2DEC(\"X\")": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"}, // HEX2OCT "HEX2OCT()": {"#VALUE!", "HEX2OCT requires at least 1 argument"}, "HEX2OCT(1,1,1)": {"#VALUE!", "HEX2OCT allows at most 2 arguments"}, "HEX2OCT(\"X\",1)": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"}, "HEX2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HEX2OCT(-513,10)": {"#NUM!", "strconv.ParseInt: parsing \"-\": invalid syntax"}, "HEX2OCT(1,-1)": {"#NUM!", "#NUM!"}, // IMABS "IMABS()": {"#VALUE!", "IMABS requires 1 argument"}, "IMABS(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMAGINARY "IMAGINARY()": {"#VALUE!", "IMAGINARY requires 1 argument"}, "IMAGINARY(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMARGUMENT "IMARGUMENT()": {"#VALUE!", "IMARGUMENT requires 1 argument"}, "IMARGUMENT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMCONJUGATE "IMCONJUGATE()": {"#VALUE!", "IMCONJUGATE requires 1 argument"}, "IMCONJUGATE(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMCOS "IMCOS()": {"#VALUE!", "IMCOS requires 1 argument"}, "IMCOS(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMCOSH "IMCOSH()": {"#VALUE!", "IMCOSH requires 1 argument"}, "IMCOSH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMCOT "IMCOT()": {"#VALUE!", "IMCOT requires 1 argument"}, "IMCOT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMCSC "IMCSC()": {"#VALUE!", "IMCSC requires 1 argument"}, "IMCSC(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMCSC(0)": {"#NUM!", "#NUM!"}, // IMCSCH "IMCSCH()": {"#VALUE!", "IMCSCH requires 1 argument"}, "IMCSCH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMCSCH(0)": {"#NUM!", "#NUM!"}, // IMDIV "IMDIV()": {"#VALUE!", "IMDIV requires 2 arguments"}, "IMDIV(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMDIV(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMDIV(1,0)": {"#NUM!", "#NUM!"}, // IMEXP "IMEXP()": {"#VALUE!", "IMEXP requires 1 argument"}, "IMEXP(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMLN "IMLN()": {"#VALUE!", "IMLN requires 1 argument"}, "IMLN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMLN(0)": {"#NUM!", "#NUM!"}, // IMLOG10 "IMLOG10()": {"#VALUE!", "IMLOG10 requires 1 argument"}, "IMLOG10(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMLOG10(0)": {"#NUM!", "#NUM!"}, // IMLOG2 "IMLOG2()": {"#VALUE!", "IMLOG2 requires 1 argument"}, "IMLOG2(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMLOG2(0)": {"#NUM!", "#NUM!"}, // IMPOWER "IMPOWER()": {"#VALUE!", "IMPOWER requires 2 arguments"}, "IMPOWER(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMPOWER(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMPOWER(0,0)": {"#NUM!", "#NUM!"}, "IMPOWER(0,-1)": {"#NUM!", "#NUM!"}, // IMPRODUCT "IMPRODUCT(\"x\")": {"#NUM!", "strconv.ParseComplex: parsing \"x\": invalid syntax"}, "IMPRODUCT(A1:D1)": {"#NUM!", "strconv.ParseComplex: parsing \"Month\": invalid syntax"}, // IMREAL "IMREAL()": {"#VALUE!", "IMREAL requires 1 argument"}, "IMREAL(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSEC "IMSEC()": {"#VALUE!", "IMSEC requires 1 argument"}, "IMSEC(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSECH "IMSECH()": {"#VALUE!", "IMSECH requires 1 argument"}, "IMSECH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSIN "IMSIN()": {"#VALUE!", "IMSIN requires 1 argument"}, "IMSIN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSINH "IMSINH()": {"#VALUE!", "IMSINH requires 1 argument"}, "IMSINH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSQRT "IMSQRT()": {"#VALUE!", "IMSQRT requires 1 argument"}, "IMSQRT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSUB "IMSUB()": {"#VALUE!", "IMSUB requires 2 arguments"}, "IMSUB(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, "IMSUB(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMSUM "IMSUM()": {"#VALUE!", "IMSUM requires at least 1 argument"}, "IMSUM(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // IMTAN "IMTAN()": {"#VALUE!", "IMTAN requires 1 argument"}, "IMTAN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"}, // OCT2BIN "OCT2BIN()": {"#VALUE!", "OCT2BIN requires at least 1 argument"}, "OCT2BIN(1,1,1)": {"#VALUE!", "OCT2BIN allows at most 2 arguments"}, "OCT2BIN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "OCT2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "OCT2BIN(-536870912 ,10)": {"#NUM!", "#NUM!"}, "OCT2BIN(1,-1)": {"#NUM!", "#NUM!"}, // OCT2DEC "OCT2DEC()": {"#VALUE!", "OCT2DEC requires 1 numeric argument"}, "OCT2DEC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // OCT2HEX "OCT2HEX()": {"#VALUE!", "OCT2HEX requires at least 1 argument"}, "OCT2HEX(1,1,1)": {"#VALUE!", "OCT2HEX allows at most 2 arguments"}, "OCT2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "OCT2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "OCT2HEX(-536870912 ,10)": {"#NUM!", "#NUM!"}, "OCT2HEX(1,-1)": {"#NUM!", "#NUM!"}, // Math and Trigonometric Functions // ABS "ABS()": {"#VALUE!", "ABS requires 1 numeric argument"}, "ABS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ABS(~)": {"#NAME?", "invalid reference"}, // ACOS "ACOS()": {"#VALUE!", "ACOS requires 1 numeric argument"}, "ACOS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ACOS(ACOS(0))": {"#NUM!", "#NUM!"}, // ACOSH "ACOSH()": {"#VALUE!", "ACOSH requires 1 numeric argument"}, "ACOSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.ACOT "_xlfn.ACOT()": {"#VALUE!", "ACOT requires 1 numeric argument"}, "_xlfn.ACOT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.ACOTH "_xlfn.ACOTH()": {"#VALUE!", "ACOTH requires 1 numeric argument"}, "_xlfn.ACOTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.ACOTH(_xlfn.ACOTH(2))": {"#NUM!", "#NUM!"}, // _xlfn.AGGREGATE "_xlfn.AGGREGATE()": {"#VALUE!", "AGGREGATE requires at least 3 arguments"}, "_xlfn.AGGREGATE(\"\",0,A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "_xlfn.AGGREGATE(1,\"\",A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "_xlfn.AGGREGATE(0,A4:A5)": {"#VALUE!", "AGGREGATE has invalid function_num"}, "_xlfn.AGGREGATE(1,8,A4:A5)": {"#VALUE!", "AGGREGATE has invalid options"}, "_xlfn.AGGREGATE(1,0,A5:A6)": {"#DIV/0!", "#DIV/0!"}, "_xlfn.AGGREGATE(13,0,A1:A6)": {"#N/A", "#N/A"}, "_xlfn.AGGREGATE(18,0,A1:A6,1)": {"#NUM!", "#NUM!"}, // _xlfn.ARABIC "_xlfn.ARABIC()": {"#VALUE!", "ARABIC requires 1 numeric argument"}, "_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": {"#VALUE!", "#VALUE!"}, // ASIN "ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"}, "ASIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ASINH "ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"}, "ASINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ATAN "ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"}, "ATAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ATANH "ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"}, "ATANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ATAN2 "ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"}, "ATAN2(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ATAN2(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // BASE "BASE()": {"#VALUE!", "BASE requires at least 2 arguments"}, "BASE(1,2,3,4)": {"#VALUE!", "BASE allows at most 3 arguments"}, "BASE(1,1)": {"#VALUE!", "radix must be an integer >= 2 and <= 36"}, "BASE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "BASE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "BASE(1,2,\"X\")": {"#VALUE!", "strconv.Atoi: parsing \"X\": invalid syntax"}, // CEILING "CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"}, "CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"}, "CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"}, "CEILING(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "CEILING(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.CEILING.MATH "_xlfn.CEILING.MATH()": {"#VALUE!", "CEILING.MATH requires at least 1 argument"}, "_xlfn.CEILING.MATH(1,2,3,4)": {"#VALUE!", "CEILING.MATH allows at most 3 arguments"}, "_xlfn.CEILING.MATH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.CEILING.MATH(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.CEILING.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.CEILING.PRECISE "_xlfn.CEILING.PRECISE()": {"#VALUE!", "CEILING.PRECISE requires at least 1 argument"}, "_xlfn.CEILING.PRECISE(1,2,3)": {"#VALUE!", "CEILING.PRECISE allows at most 2 arguments"}, "_xlfn.CEILING.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.CEILING.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // COMBIN "COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"}, "COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"}, "COMBIN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "COMBIN(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.COMBINA "_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"}, "_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"}, "_xlfn.COMBINA(-1,-1)": {"#VALUE!", "COMBIN requires number >= number_chosen"}, "_xlfn.COMBINA(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.COMBINA(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // COS "COS()": {"#VALUE!", "COS requires 1 numeric argument"}, "COS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // COSH "COSH()": {"#VALUE!", "COSH requires 1 numeric argument"}, "COSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.COT "COT()": {"#VALUE!", "COT requires 1 numeric argument"}, "COT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "COT(0)": {"#DIV/0!", "#DIV/0!"}, // _xlfn.COTH "COTH()": {"#VALUE!", "COTH requires 1 numeric argument"}, "COTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "COTH(0)": {"#DIV/0!", "#DIV/0!"}, // _xlfn.CSC "_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"}, "_xlfn.CSC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"}, // _xlfn.CSCH "_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"}, "_xlfn.CSCH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"}, // _xlfn.DECIMAL "_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"}, "_xlfn.DECIMAL(\"X\",2)": {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"}, "_xlfn.DECIMAL(2000,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // DEGREES "DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"}, "DEGREES(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "DEGREES(0)": {"#DIV/0!", "#DIV/0!"}, // EVEN "EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"}, "EVEN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // EXP "EXP()": {"#VALUE!", "EXP requires 1 numeric argument"}, "EXP(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // FACT "FACT()": {"#VALUE!", "FACT requires 1 numeric argument"}, "FACT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "FACT(-1)": {"#NUM!", "#NUM!"}, // FACTDOUBLE "FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"}, "FACTDOUBLE(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "FACTDOUBLE(-1)": {"#NUM!", "#NUM!"}, // FLOOR "FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"}, "FLOOR(\"X\",-1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "FLOOR(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "FLOOR(1,-1)": {"#NUM!", "invalid arguments to FLOOR"}, // _xlfn.FLOOR.MATH "_xlfn.FLOOR.MATH()": {"#VALUE!", "FLOOR.MATH requires at least 1 argument"}, "_xlfn.FLOOR.MATH(1,2,3,4)": {"#VALUE!", "FLOOR.MATH allows at most 3 arguments"}, "_xlfn.FLOOR.MATH(\"X\",2,3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.FLOOR.MATH(1,\"X\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.FLOOR.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.FLOOR.PRECISE "_xlfn.FLOOR.PRECISE()": {"#VALUE!", "FLOOR.PRECISE requires at least 1 argument"}, "_xlfn.FLOOR.PRECISE(1,2,3)": {"#VALUE!", "FLOOR.PRECISE allows at most 2 arguments"}, "_xlfn.FLOOR.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.FLOOR.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // GCD "GCD()": {"#VALUE!", "GCD requires at least 1 argument"}, "GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"}, "GCD(1,-1)": {"#VALUE!", "GCD only accepts positive arguments"}, "GCD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // INT "INT()": {"#VALUE!", "INT requires 1 numeric argument"}, "INT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ISO.CEILING "ISO.CEILING()": {"#VALUE!", "ISO.CEILING requires at least 1 argument"}, "ISO.CEILING(1,2,3)": {"#VALUE!", "ISO.CEILING allows at most 2 arguments"}, "ISO.CEILING(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ISO.CEILING(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // LCM "LCM()": {"#VALUE!", "LCM requires at least 1 argument"}, "LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"}, "LCM(1,-1)": {"#VALUE!", "LCM only accepts positive arguments"}, "LCM(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // LN "LN()": {"#VALUE!", "LN requires 1 numeric argument"}, "LN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // LOG "LOG()": {"#VALUE!", "LOG requires at least 1 argument"}, "LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"}, "LOG(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "LOG(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "LOG(0,0)": {"#NUM!", "#DIV/0!"}, "LOG(1,0)": {"#NUM!", "#DIV/0!"}, "LOG(1,1)": {"#DIV/0!", "#DIV/0!"}, // LOG10 "LOG10()": {"#VALUE!", "LOG10 requires 1 numeric argument"}, "LOG10(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // MDETERM "MDETERM()": {"#VALUE!", "MDETERM requires 1 argument"}, // MINVERSE "MINVERSE()": {"#VALUE!", "MINVERSE requires 1 argument"}, "MINVERSE(B3:C4)": {"#VALUE!", "#VALUE!"}, "MINVERSE(A1:C2)": {"#VALUE!", "#VALUE!"}, "MINVERSE(A4:A4)": {"#NUM!", "#NUM!"}, // MMULT "MMULT()": {"#VALUE!", "MMULT requires 2 argument"}, "MMULT(A1:B2,B3:C4)": {"#VALUE!", "#VALUE!"}, "MMULT(B3:C4,A1:B2)": {"#VALUE!", "#VALUE!"}, "MMULT(A1:A2,B1:B2)": {"#VALUE!", "#VALUE!"}, // MOD "MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"}, "MOD(6,0)": {"#DIV/0!", "MOD divide by zero"}, "MOD(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "MOD(6,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // MROUND "MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"}, "MROUND(1,0)": {"#NUM!", "#NUM!"}, "MROUND(1,-1)": {"#NUM!", "#NUM!"}, "MROUND(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "MROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // MULTINOMIAL "MULTINOMIAL(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.MUNIT "_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"}, "_xlfn.MUNIT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "_xlfn.MUNIT(-1)": {"#VALUE!", ""}, // ODD "ODD()": {"#VALUE!", "ODD requires 1 numeric argument"}, "ODD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // PI "PI(1)": {"#VALUE!", "PI accepts no arguments"}, // POWER "POWER(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "POWER(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "POWER(0,0)": {"#NUM!", "#NUM!"}, "POWER(0,-1)": {"#DIV/0!", "#DIV/0!"}, "POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"}, // PRODUCT "PRODUCT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "PRODUCT(\"\",3,6)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // QUOTIENT "QUOTIENT(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "QUOTIENT(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "QUOTIENT(1,0)": {"#DIV/0!", "#DIV/0!"}, "QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"}, // RADIANS "RADIANS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"}, // RAND "RAND(1)": {"#VALUE!", "RAND accepts no arguments"}, // RANDBETWEEN "RANDBETWEEN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "RANDBETWEEN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "RANDBETWEEN()": {"#VALUE!", "RANDBETWEEN requires 2 numeric arguments"}, "RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"}, // ROMAN "ROMAN()": {"#VALUE!", "ROMAN requires at least 1 argument"}, "ROMAN(1,2,3)": {"#VALUE!", "ROMAN allows at most 2 arguments"}, "ROMAN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ROMAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ROMAN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ROUND "ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"}, "ROUND(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ROUNDDOWN "ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"}, "ROUNDDOWN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ROUNDDOWN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // ROUNDUP "ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"}, "ROUNDUP(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "ROUNDUP(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // SEARCH "SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"}, "SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"}, "SEARCH(2,A1)": {"#VALUE!", "#VALUE!"}, "SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEARCHB "SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"}, "SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"}, "SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"}, "SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"}, "SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEC "_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"}, "_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // _xlfn.SECH "_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"}, "_xlfn.SECH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // SERIESSUM "SERIESSUM()": {"#VALUE!", "SERIESSUM requires 4 arguments"}, "SERIESSUM(\"\",2,3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SERIESSUM(1,\"\",3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SERIESSUM(1,2,\"\",A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SERIESSUM(1,2,3,A1:D1)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"}, // SIGN "SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"}, "SIGN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // SIN "SIN()": {"#VALUE!", "SIN requires 1 numeric argument"}, "SIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // SINH "SINH()": {"#VALUE!", "SINH requires 1 numeric argument"}, "SINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // SQRT "SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"}, "SQRT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SQRT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "SQRT(-1)": {"#NUM!", "#NUM!"}, // SQRTPI "SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"}, "SQRTPI(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // STDEV "STDEV()": {"#VALUE!", "STDEV requires at least 1 argument"}, "STDEV(E2:E9)": {"#DIV/0!", "#DIV/0!"}, // STDEV.S "STDEV.S()": {"#VALUE!", "STDEV.S requires at least 1 argument"}, // STDEVA "STDEVA()": {"#VALUE!", "STDEVA requires at least 1 argument"}, "STDEVA(E2:E9)": {"#DIV/0!", "#DIV/0!"}, // POISSON.DIST "POISSON.DIST()": {"#VALUE!", "POISSON.DIST requires 3 arguments"}, // POISSON "POISSON()": {"#VALUE!", "POISSON requires 3 arguments"}, "POISSON(\"\",0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "POISSON(0,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "POISSON(0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "POISSON(0,-1,TRUE)": {"#N/A", "#N/A"}, // PROB "PROB()": {"#VALUE!", "PROB requires at least 3 arguments"}, "PROB(A1:A2,B1:B2,1,1,1)": {"#VALUE!", "PROB requires at most 4 arguments"}, "PROB(A1:A2,B1:B2,\"\")": {"#VALUE!", "#VALUE!"}, "PROB(A1:A2,B1:B2,1,\"\")": {"#VALUE!", "#VALUE!"}, "PROB(A1,B1,1)": {"#NUM!", "#NUM!"}, "PROB(A1:A2,B1:B3,1)": {"#N/A", "#N/A"}, "PROB(A1:A2,B1:C2,1)": {"#N/A", "#N/A"}, "PROB(A1:A2,B1:B2,1)": {"#NUM!", "#NUM!"}, // SUBTOTAL "SUBTOTAL()": {"#VALUE!", "SUBTOTAL requires at least 2 arguments"}, "SUBTOTAL(\"\",A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SUBTOTAL(0,A4:A5)": {"#VALUE!", "SUBTOTAL has invalid function_num"}, "SUBTOTAL(1,A5:A6)": {"#DIV/0!", "#DIV/0!"}, // SUM "SUM((": {"", ErrInvalidFormula.Error()}, "SUM(-)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()}, "SUM(1+)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()}, "SUM(1-)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()}, "SUM(1*)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()}, "SUM(1/)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()}, "SUM(1*SUM(1/0))": {"#DIV/0!", "#DIV/0!"}, "SUM(1*SUM(1/0)*1)": {"", "#DIV/0!"}, "SUM(0:2)": {"#NAME?", "invalid reference"}, "SUM(1:1048577)": {"#NAME?", "invalid reference"}, // SUMIF "SUMIF()": {"#VALUE!", "SUMIF requires at least 2 arguments"}, // SUMSQ "SUMSQ(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "SUMSQ(C1:D2)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"}, // SUMPRODUCT "SUMPRODUCT()": {"#VALUE!", "SUMPRODUCT requires at least 1 argument"}, "SUMPRODUCT(A1,B1:B2)": {"#VALUE!", "#VALUE!"}, "SUMPRODUCT(A1,D1)": {"#VALUE!", "#VALUE!"}, "SUMPRODUCT(A1:A3,D1:D3)": {"#VALUE!", "#VALUE!"}, "SUMPRODUCT(A1:A2,B1:B3)": {"#VALUE!", "#VALUE!"}, "SUMPRODUCT(\"\")": {"#VALUE!", "#VALUE!"}, "SUMPRODUCT(A1,NA())": {"#N/A", "#N/A"}, // SUMX2MY2 "SUMX2MY2()": {"#VALUE!", "SUMX2MY2 requires 2 arguments"}, "SUMX2MY2(A1,B1:B2)": {"#N/A", "#N/A"}, // SUMX2PY2 "SUMX2PY2()": {"#VALUE!", "SUMX2PY2 requires 2 arguments"}, "SUMX2PY2(A1,B1:B2)": {"#N/A", "#N/A"}, // SUMXMY2 "SUMXMY2()": {"#VALUE!", "SUMXMY2 requires 2 arguments"}, "SUMXMY2(A1,B1:B2)": {"#N/A", "#N/A"}, // TAN "TAN()": {"#VALUE!", "TAN requires 1 numeric argument"}, "TAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // TANH "TANH()": {"#VALUE!", "TANH requires 1 numeric argument"}, "TANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // TRUNC "TRUNC()": {"#VALUE!", "TRUNC requires at least 1 argument"}, "TRUNC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "TRUNC(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, // Statistical Functions // AVEDEV "AVEDEV()": {"#VALUE!", "AVEDEV requires at least 1 argument"}, "AVEDEV(\"\")": {"#VALUE!", "#VALUE!"}, "AVEDEV(1,\"\")": {"#VALUE!", "#VALUE!"}, // AVERAGE "AVERAGE(H1)": {"#DIV/0!", "#DIV/0!"}, // AVERAGEA "AVERAGEA(H1)": {"#DIV/0!", "#DIV/0!"}, // AVERAGEIF "AVERAGEIF()": {"#VALUE!", "AVERAGEIF requires at least 2 arguments"}, "AVERAGEIF(H1,\"\")": {"#DIV/0!", "#DIV/0!"}, "AVERAGEIF(D1:D3,\"Month\",D1:D3)": {"#DIV/0!", "#DIV/0!"}, "AVERAGEIF(C1:C3,\"Month\",D1:D3)": {"#DIV/0!", "#DIV/0!"}, // BETA.DIST "BETA.DIST()": {"#VALUE!", "BETA.DIST requires at least 4 arguments"}, "BETA.DIST(0.4,4,5,TRUE,0,1,0)": {"#VALUE!", "BETA.DIST requires at most 6 arguments"}, "BETA.DIST(\"\",4,5,TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,\"\",5,TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,4,\"\",TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,4,5,\"\",0,1)": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,4,5,TRUE,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,4,5,TRUE,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.DIST(0.4,0,5,TRUE,0,1)": {"#NUM!", "#NUM!"}, "BETA.DIST(0.4,4,0,TRUE,0,0)": {"#NUM!", "#NUM!"}, "BETA.DIST(0.4,4,5,TRUE,0.5,1)": {"#NUM!", "#NUM!"}, "BETA.DIST(0.4,4,5,TRUE,0,0.3)": {"#NUM!", "#NUM!"}, "BETA.DIST(0.4,4,5,TRUE,0.4,0.4)": {"#NUM!", "#NUM!"}, // BETADIST "BETADIST()": {"#VALUE!", "BETADIST requires at least 3 arguments"}, "BETADIST(0.4,4,5,0,1,0)": {"#VALUE!", "BETADIST requires at most 5 arguments"}, "BETADIST(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETADIST(0.4,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETADIST(0.4,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETADIST(0.4,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETADIST(0.4,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETADIST(2,4,5,3,1)": {"#NUM!", "#NUM!"}, "BETADIST(2,4,5,0,1)": {"#NUM!", "#NUM!"}, "BETADIST(0.4,0,5,0,1)": {"#NUM!", "#NUM!"}, "BETADIST(0.4,4,0,0,1)": {"#NUM!", "#NUM!"}, "BETADIST(0.4,4,5,0.4,0.4)": {"#NUM!", "#NUM!"}, // BETAINV "BETAINV()": {"#VALUE!", "BETAINV requires at least 3 arguments"}, "BETAINV(0.2,4,5,0,1,0)": {"#VALUE!", "BETAINV requires at most 5 arguments"}, "BETAINV(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETAINV(0.2,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETAINV(0.2,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETAINV(0.2,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETAINV(0.2,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETAINV(0,4,5,0,1)": {"#NUM!", "#NUM!"}, "BETAINV(1,4,5,0,1)": {"#NUM!", "#NUM!"}, "BETAINV(0.2,0,5,0,1)": {"#NUM!", "#NUM!"}, "BETAINV(0.2,4,0,0,1)": {"#NUM!", "#NUM!"}, "BETAINV(0.2,4,5,2,2)": {"#NUM!", "#NUM!"}, // BETA.INV "BETA.INV()": {"#VALUE!", "BETA.INV requires at least 3 arguments"}, "BETA.INV(0.2,4,5,0,1,0)": {"#VALUE!", "BETA.INV requires at most 5 arguments"}, "BETA.INV(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.INV(0.2,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.INV(0.2,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.INV(0.2,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.INV(0.2,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BETA.INV(0,4,5,0,1)": {"#NUM!", "#NUM!"}, "BETA.INV(1,4,5,0,1)": {"#NUM!", "#NUM!"}, "BETA.INV(0.2,0,5,0,1)": {"#NUM!", "#NUM!"}, "BETA.INV(0.2,4,0,0,1)": {"#NUM!", "#NUM!"}, "BETA.INV(0.2,4,5,2,2)": {"#NUM!", "#NUM!"}, // BINOMDIST "BINOMDIST()": {"#VALUE!", "BINOMDIST requires 4 arguments"}, "BINOMDIST(\"\",100,0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOMDIST(10,\"\",0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOMDIST(10,100,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOMDIST(10,100,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "BINOMDIST(-1,100,0.5,FALSE)": {"#NUM!", "#NUM!"}, "BINOMDIST(110,100,0.5,FALSE)": {"#NUM!", "#NUM!"}, "BINOMDIST(10,100,-1,FALSE)": {"#NUM!", "#NUM!"}, "BINOMDIST(10,100,2,FALSE)": {"#NUM!", "#NUM!"}, // BINOM.DIST "BINOM.DIST()": {"#VALUE!", "BINOM.DIST requires 4 arguments"}, "BINOM.DIST(\"\",100,0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST(10,\"\",0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST(10,100,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST(10,100,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "BINOM.DIST(-1,100,0.5,FALSE)": {"#NUM!", "#NUM!"}, "BINOM.DIST(110,100,0.5,FALSE)": {"#NUM!", "#NUM!"}, "BINOM.DIST(10,100,-1,FALSE)": {"#NUM!", "#NUM!"}, "BINOM.DIST(10,100,2,FALSE)": {"#NUM!", "#NUM!"}, // BINOM.DIST.RANGE "BINOM.DIST.RANGE()": {"#VALUE!", "BINOM.DIST.RANGE requires at least 3 arguments"}, "BINOM.DIST.RANGE(100,0.5,0,40,0)": {"#VALUE!", "BINOM.DIST.RANGE requires at most 4 arguments"}, "BINOM.DIST.RANGE(\"\",0.5,0,40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST.RANGE(100,\"\",0,40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST.RANGE(100,0.5,\"\",40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST.RANGE(100,0.5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.DIST.RANGE(100,-1,0,40)": {"#NUM!", "#NUM!"}, "BINOM.DIST.RANGE(100,2,0,40)": {"#NUM!", "#NUM!"}, "BINOM.DIST.RANGE(100,0.5,-1,40)": {"#NUM!", "#NUM!"}, "BINOM.DIST.RANGE(100,0.5,110,40)": {"#NUM!", "#NUM!"}, "BINOM.DIST.RANGE(100,0.5,0,-1)": {"#NUM!", "#NUM!"}, "BINOM.DIST.RANGE(100,0.5,0,110)": {"#NUM!", "#NUM!"}, // BINOM.INV "BINOM.INV()": {"#VALUE!", "BINOM.INV requires 3 numeric arguments"}, "BINOM.INV(\"\",0.5,20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.INV(100,\"\",20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.INV(100,0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "BINOM.INV(-1,0.5,20%)": {"#NUM!", "#NUM!"}, "BINOM.INV(100,-1,20%)": {"#NUM!", "#NUM!"}, "BINOM.INV(100,2,20%)": {"#NUM!", "#NUM!"}, "BINOM.INV(100,0.5,-1)": {"#NUM!", "#NUM!"}, "BINOM.INV(100,0.5,2)": {"#NUM!", "#NUM!"}, "BINOM.INV(1,1,20%)": {"#NUM!", "#NUM!"}, // CHIDIST "CHIDIST()": {"#VALUE!", "CHIDIST requires 2 numeric arguments"}, "CHIDIST(\"\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHIDIST(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // CHIINV "CHIINV()": {"#VALUE!", "CHIINV requires 2 numeric arguments"}, "CHIINV(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHIINV(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHIINV(0,1)": {"#NUM!", "#NUM!"}, "CHIINV(2,1)": {"#NUM!", "#NUM!"}, "CHIINV(0.5,0.5)": {"#NUM!", "#NUM!"}, // CHISQ.DIST "CHISQ.DIST()": {"#VALUE!", "CHISQ.DIST requires 3 arguments"}, "CHISQ.DIST(\"\",2,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.DIST(3,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.DIST(3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "CHISQ.DIST(-1,2,TRUE)": {"#NUM!", "#NUM!"}, "CHISQ.DIST(3,0,TRUE)": {"#NUM!", "#NUM!"}, // CHISQ.DIST.RT "CHISQ.DIST.RT()": {"#VALUE!", "CHISQ.DIST.RT requires 2 numeric arguments"}, "CHISQ.DIST.RT(\"\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.DIST.RT(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // CHISQ.INV "CHISQ.INV()": {"#VALUE!", "CHISQ.INV requires 2 numeric arguments"}, "CHISQ.INV(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.INV(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.INV(-1,1)": {"#NUM!", "#NUM!"}, "CHISQ.INV(1,1)": {"#NUM!", "#NUM!"}, "CHISQ.INV(0.5,0.5)": {"#NUM!", "#NUM!"}, "CHISQ.INV(0.5,10000000001)": {"#NUM!", "#NUM!"}, // CHISQ.INV.RT "CHISQ.INV.RT()": {"#VALUE!", "CHISQ.INV.RT requires 2 numeric arguments"}, "CHISQ.INV.RT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.INV.RT(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CHISQ.INV.RT(0,1)": {"#NUM!", "#NUM!"}, "CHISQ.INV.RT(2,1)": {"#NUM!", "#NUM!"}, "CHISQ.INV.RT(0.5,0.5)": {"#NUM!", "#NUM!"}, // CONFIDENCE "CONFIDENCE()": {"#VALUE!", "CONFIDENCE requires 3 numeric arguments"}, "CONFIDENCE(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE(0,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE(1,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE(0.05,0,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE(0.05,0.07,0.5)": {"#NUM!", "#NUM!"}, // CONFIDENCE.NORM "CONFIDENCE.NORM()": {"#VALUE!", "CONFIDENCE.NORM requires 3 numeric arguments"}, "CONFIDENCE.NORM(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.NORM(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.NORM(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.NORM(0,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.NORM(1,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.NORM(0.05,0,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.NORM(0.05,0.07,0.5)": {"#NUM!", "#NUM!"}, // CORREL "CORREL()": {"#VALUE!", "CORREL requires 2 arguments"}, "CORREL(A1:A3,B1:B5)": {"#N/A", "#N/A"}, "CORREL(A1:A1,B1:B1)": {"#DIV/0!", "#DIV/0!"}, // CONFIDENCE.T "CONFIDENCE.T()": {"#VALUE!", "CONFIDENCE.T requires 3 arguments"}, "CONFIDENCE.T(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.T(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.T(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CONFIDENCE.T(0,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.T(1,0.07,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.T(0.05,0,100)": {"#NUM!", "#NUM!"}, "CONFIDENCE.T(0.05,0.07,0)": {"#NUM!", "#NUM!"}, "CONFIDENCE.T(0.05,0.07,1)": {"#DIV/0!", "#DIV/0!"}, // COUNTBLANK "COUNTBLANK()": {"#VALUE!", "COUNTBLANK requires 1 argument"}, "COUNTBLANK(1,2)": {"#VALUE!", "COUNTBLANK requires 1 argument"}, // COUNTIF "COUNTIF()": {"#VALUE!", "COUNTIF requires 2 arguments"}, // COUNTIFS "COUNTIFS()": {"#VALUE!", "COUNTIFS requires at least 2 arguments"}, "COUNTIFS(A1:A9,2,D1:D9)": {"#N/A", "#N/A"}, // CRITBINOM "CRITBINOM()": {"#VALUE!", "CRITBINOM requires 3 numeric arguments"}, "CRITBINOM(\"\",0.5,20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CRITBINOM(100,\"\",20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CRITBINOM(100,0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CRITBINOM(-1,0.5,20%)": {"#NUM!", "#NUM!"}, "CRITBINOM(100,-1,20%)": {"#NUM!", "#NUM!"}, "CRITBINOM(100,2,20%)": {"#NUM!", "#NUM!"}, "CRITBINOM(100,0.5,-1)": {"#NUM!", "#NUM!"}, "CRITBINOM(100,0.5,2)": {"#NUM!", "#NUM!"}, "CRITBINOM(1,1,20%)": {"#NUM!", "#NUM!"}, // DEVSQ "DEVSQ()": {"#VALUE!", "DEVSQ requires at least 1 numeric argument"}, "DEVSQ(D1:D2)": {"#N/A", "#N/A"}, // FISHER "FISHER()": {"#VALUE!", "FISHER requires 1 numeric argument"}, "FISHER(2)": {"#N/A", "#N/A"}, "FISHER(\"2\")": {"#N/A", "#N/A"}, "FISHER(INT(-2)))": {"#N/A", "#N/A"}, "FISHER(F1)": {"#VALUE!", "FISHER requires 1 numeric argument"}, // FISHERINV "FISHERINV()": {"#VALUE!", "FISHERINV requires 1 numeric argument"}, "FISHERINV(F1)": {"#VALUE!", "FISHERINV requires 1 numeric argument"}, // FORECAST "FORECAST()": {"#VALUE!", "FORECAST requires 3 arguments"}, "FORECAST(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FORECAST(1,A1:A2,B1:B1)": {"#N/A", "#N/A"}, "FORECAST(1,A4,A4)": {"#DIV/0!", "#DIV/0!"}, // FORECAST.LINEAR "FORECAST.LINEAR()": {"#VALUE!", "FORECAST.LINEAR requires 3 arguments"}, "FORECAST.LINEAR(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FORECAST.LINEAR(1,A1:A2,B1:B1)": {"#N/A", "#N/A"}, "FORECAST.LINEAR(1,A4,A4)": {"#DIV/0!", "#DIV/0!"}, // FREQUENCY "FREQUENCY()": {"#VALUE!", "FREQUENCY requires 2 arguments"}, "FREQUENCY(NA(),A1:A3)": {"#N/A", "#N/A"}, "FREQUENCY(A1:A3,NA())": {"#N/A", "#N/A"}, // GAMMA "GAMMA()": {"#VALUE!", "GAMMA requires 1 numeric argument"}, "GAMMA(F1)": {"#VALUE!", "GAMMA requires 1 numeric argument"}, "GAMMA(0)": {"#N/A", "#N/A"}, "GAMMA(\"0\")": {"#N/A", "#N/A"}, "GAMMA(INT(0))": {"#N/A", "#N/A"}, // GAMMA.DIST "GAMMA.DIST()": {"#VALUE!", "GAMMA.DIST requires 4 arguments"}, "GAMMA.DIST(\"\",3,2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.DIST(6,\"\",2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.DIST(6,3,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.DIST(6,3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "GAMMA.DIST(-1,3,2,FALSE)": {"#NUM!", "#NUM!"}, "GAMMA.DIST(6,0,2,FALSE)": {"#NUM!", "#NUM!"}, "GAMMA.DIST(6,3,0,FALSE)": {"#NUM!", "#NUM!"}, // GAMMADIST "GAMMADIST()": {"#VALUE!", "GAMMADIST requires 4 arguments"}, "GAMMADIST(\"\",3,2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMADIST(6,\"\",2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMADIST(6,3,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMADIST(6,3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "GAMMADIST(-1,3,2,FALSE)": {"#NUM!", "#NUM!"}, "GAMMADIST(6,0,2,FALSE)": {"#NUM!", "#NUM!"}, "GAMMADIST(6,3,0,FALSE)": {"#NUM!", "#NUM!"}, // GAMMA.INV "GAMMA.INV()": {"#VALUE!", "GAMMA.INV requires 3 arguments"}, "GAMMA.INV(\"\",3,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.INV(0.5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.INV(0.5,3,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMA.INV(-1,3,2)": {"#NUM!", "#NUM!"}, "GAMMA.INV(2,3,2)": {"#NUM!", "#NUM!"}, "GAMMA.INV(0.5,0,2)": {"#NUM!", "#NUM!"}, "GAMMA.INV(0.5,3,0)": {"#NUM!", "#NUM!"}, // GAMMAINV "GAMMAINV()": {"#VALUE!", "GAMMAINV requires 3 arguments"}, "GAMMAINV(\"\",3,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMAINV(0.5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMAINV(0.5,3,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMAINV(-1,3,2)": {"#NUM!", "#NUM!"}, "GAMMAINV(2,3,2)": {"#NUM!", "#NUM!"}, "GAMMAINV(0.5,0,2)": {"#NUM!", "#NUM!"}, "GAMMAINV(0.5,3,0)": {"#NUM!", "#NUM!"}, // GAMMALN "GAMMALN()": {"#VALUE!", "GAMMALN requires 1 numeric argument"}, "GAMMALN(F1)": {"#VALUE!", "GAMMALN requires 1 numeric argument"}, "GAMMALN(0)": {"#N/A", "#N/A"}, "GAMMALN(INT(0))": {"#N/A", "#N/A"}, // GAMMALN.PRECISE "GAMMALN.PRECISE()": {"#VALUE!", "GAMMALN.PRECISE requires 1 numeric argument"}, "GAMMALN.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "GAMMALN.PRECISE(0)": {"#NUM!", "#NUM!"}, // GAUSS "GAUSS()": {"#VALUE!", "GAUSS requires 1 numeric argument"}, "GAUSS(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // GEOMEAN "GEOMEAN()": {"#VALUE!", "GEOMEAN requires at least 1 numeric argument"}, "GEOMEAN(0)": {"#NUM!", "#NUM!"}, "GEOMEAN(D1:D2)": {"#NUM!", "#NUM!"}, "GEOMEAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // HARMEAN "HARMEAN()": {"#VALUE!", "HARMEAN requires at least 1 argument"}, "HARMEAN(-1)": {"#N/A", "#N/A"}, "HARMEAN(0)": {"#N/A", "#N/A"}, // HYPGEOM.DIST "HYPGEOM.DIST()": {"#VALUE!", "HYPGEOM.DIST requires 5 arguments"}, "HYPGEOM.DIST(\"\",4,4,12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOM.DIST(1,\"\",4,12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOM.DIST(1,4,\"\",12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOM.DIST(1,4,4,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOM.DIST(1,4,4,12,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "HYPGEOM.DIST(-1,4,4,12,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(2,1,4,12,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(2,4,1,12,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(2,2,2,1,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(1,0,4,12,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(1,4,4,2,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(1,4,0,12,FALSE)": {"#NUM!", "#NUM!"}, "HYPGEOM.DIST(1,4,4,0,FALSE)": {"#NUM!", "#NUM!"}, // HYPGEOMDIST "HYPGEOMDIST()": {"#VALUE!", "HYPGEOMDIST requires 4 numeric arguments"}, "HYPGEOMDIST(\"\",4,4,12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOMDIST(1,\"\",4,12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOMDIST(1,4,\"\",12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOMDIST(1,4,4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "HYPGEOMDIST(-1,4,4,12)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(2,1,4,12)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(2,4,1,12)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(2,2,2,1)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(1,0,4,12)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(1,4,4,2)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(1,4,0,12)": {"#NUM!", "#NUM!"}, "HYPGEOMDIST(1,4,4,0)": {"#NUM!", "#NUM!"}, // INTERCEPT "INTERCEPT()": {"#VALUE!", "INTERCEPT requires 2 arguments"}, "INTERCEPT(A1:A2,B1:B1)": {"#N/A", "#N/A"}, "INTERCEPT(A4,A4)": {"#DIV/0!", "#DIV/0!"}, // KURT "KURT()": {"#VALUE!", "KURT requires at least 1 argument"}, "KURT(F1,INT(1))": {"#DIV/0!", "#DIV/0!"}, // EXPON.DIST "EXPON.DIST()": {"#VALUE!", "EXPON.DIST requires 3 arguments"}, "EXPON.DIST(\"\",1,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EXPON.DIST(0,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EXPON.DIST(0,1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "EXPON.DIST(-1,1,TRUE)": {"#NUM!", "#NUM!"}, "EXPON.DIST(1,0,TRUE)": {"#NUM!", "#NUM!"}, // EXPONDIST "EXPONDIST()": {"#VALUE!", "EXPONDIST requires 3 arguments"}, "EXPONDIST(\"\",1,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EXPONDIST(0,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EXPONDIST(0,1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "EXPONDIST(-1,1,TRUE)": {"#NUM!", "#NUM!"}, "EXPONDIST(1,0,TRUE)": {"#NUM!", "#NUM!"}, // FDIST "FDIST()": {"#VALUE!", "FDIST requires 3 arguments"}, "FDIST(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FDIST(5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FDIST(5,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FDIST(-1,1,2)": {"#NUM!", "#NUM!"}, "FDIST(5,0,2)": {"#NUM!", "#NUM!"}, "FDIST(5,10000000000,2)": {"#NUM!", "#NUM!"}, "FDIST(5,1,0)": {"#NUM!", "#NUM!"}, "FDIST(5,1,10000000000)": {"#NUM!", "#NUM!"}, // F.DIST "F.DIST()": {"#VALUE!", "F.DIST requires 4 arguments"}, "F.DIST(\"\",2,5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST(1,\"\",5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST(1,2,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST(1,2,5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "F.DIST(-1,1,2,TRUE)": {"#NUM!", "#NUM!"}, "F.DIST(5,0,2,TRUE)": {"#NUM!", "#NUM!"}, "F.DIST(5,10000000000,2,TRUE)": {"#NUM!", "#NUM!"}, "F.DIST(5,1,0,TRUE)": {"#NUM!", "#NUM!"}, "F.DIST(5,1,10000000000,TRUE)": {"#NUM!", "#NUM!"}, // F.DIST.RT "F.DIST.RT()": {"#VALUE!", "F.DIST.RT requires 3 arguments"}, "F.DIST.RT(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST.RT(5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST.RT(5,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.DIST.RT(-1,1,2)": {"#NUM!", "#NUM!"}, "F.DIST.RT(5,0,2)": {"#NUM!", "#NUM!"}, "F.DIST.RT(5,10000000000,2)": {"#NUM!", "#NUM!"}, "F.DIST.RT(5,1,0)": {"#NUM!", "#NUM!"}, "F.DIST.RT(5,1,10000000000)": {"#NUM!", "#NUM!"}, // F.INV "F.INV()": {"#VALUE!", "F.INV requires 3 arguments"}, "F.INV(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV(0,1,2)": {"#NUM!", "#NUM!"}, "F.INV(0.2,0.5,2)": {"#NUM!", "#NUM!"}, "F.INV(0.2,1,0.5)": {"#NUM!", "#NUM!"}, // FINV "FINV()": {"#VALUE!", "FINV requires 3 arguments"}, "FINV(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FINV(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FINV(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FINV(0,1,2)": {"#NUM!", "#NUM!"}, "FINV(0.2,0.5,2)": {"#NUM!", "#NUM!"}, "FINV(0.2,1,0.5)": {"#NUM!", "#NUM!"}, // F.INV.RT "F.INV.RT()": {"#VALUE!", "F.INV.RT requires 3 arguments"}, "F.INV.RT(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV.RT(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV.RT(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "F.INV.RT(0,1,2)": {"#NUM!", "#NUM!"}, "F.INV.RT(0.2,0.5,2)": {"#NUM!", "#NUM!"}, "F.INV.RT(0.2,1,0.5)": {"#NUM!", "#NUM!"}, // LOGINV "LOGINV()": {"#VALUE!", "LOGINV requires 3 arguments"}, "LOGINV(\"\",2,0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGINV(0.3,\"\",0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGINV(0.3,2,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGINV(0,2,0.2)": {"#NUM!", "#NUM!"}, "LOGINV(1,2,0.2)": {"#NUM!", "#NUM!"}, "LOGINV(0.3,2,0)": {"#NUM!", "#NUM!"}, // LOGNORM.INV "LOGNORM.INV()": {"#VALUE!", "LOGNORM.INV requires 3 arguments"}, "LOGNORM.INV(\"\",2,0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.INV(0.3,\"\",0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.INV(0.3,2,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.INV(0,2,0.2)": {"#NUM!", "#NUM!"}, "LOGNORM.INV(1,2,0.2)": {"#NUM!", "#NUM!"}, "LOGNORM.INV(0.3,2,0)": {"#NUM!", "#NUM!"}, // LOGNORM.DIST "LOGNORM.DIST()": {"#VALUE!", "LOGNORM.DIST requires 4 arguments"}, "LOGNORM.DIST(\"\",10,5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.DIST(0.5,\"\",5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.DIST(0.5,10,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORM.DIST(0.5,10,5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "LOGNORM.DIST(0,10,5,FALSE)": {"#NUM!", "#NUM!"}, "LOGNORM.DIST(0.5,10,0,FALSE)": {"#NUM!", "#NUM!"}, // LOGNORMDIST "LOGNORMDIST()": {"#VALUE!", "LOGNORMDIST requires 3 arguments"}, "LOGNORMDIST(\"\",10,5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORMDIST(12,\"\",5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORMDIST(12,10,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LOGNORMDIST(0,2,5)": {"#NUM!", "#NUM!"}, "LOGNORMDIST(12,10,0)": {"#NUM!", "#NUM!"}, // NEGBINOM.DIST "NEGBINOM.DIST()": {"#VALUE!", "NEGBINOM.DIST requires 4 arguments"}, "NEGBINOM.DIST(\"\",12,0.5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOM.DIST(6,\"\",0.5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOM.DIST(6,12,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOM.DIST(6,12,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "NEGBINOM.DIST(-1,12,0.5,TRUE)": {"#NUM!", "#NUM!"}, "NEGBINOM.DIST(6,0,0.5,TRUE)": {"#NUM!", "#NUM!"}, "NEGBINOM.DIST(6,12,-1,TRUE)": {"#NUM!", "#NUM!"}, "NEGBINOM.DIST(6,12,2,TRUE)": {"#NUM!", "#NUM!"}, // NEGBINOMDIST "NEGBINOMDIST()": {"#VALUE!", "NEGBINOMDIST requires 3 arguments"}, "NEGBINOMDIST(\"\",12,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOMDIST(6,\"\",0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOMDIST(6,12,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NEGBINOMDIST(-1,12,0.5)": {"#NUM!", "#NUM!"}, "NEGBINOMDIST(6,0,0.5)": {"#NUM!", "#NUM!"}, "NEGBINOMDIST(6,12,-1)": {"#NUM!", "#NUM!"}, "NEGBINOMDIST(6,12,2)": {"#NUM!", "#NUM!"}, // NORM.DIST "NORM.DIST()": {"#VALUE!", "NORM.DIST requires 4 arguments"}, // NORMDIST "NORMDIST()": {"#VALUE!", "NORMDIST requires 4 arguments"}, "NORMDIST(\"\",0,0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMDIST(0,\"\",0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMDIST(0,0,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMDIST(0,0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "NORMDIST(0,0,-1,TRUE)": {"#N/A", "#N/A"}, // NORM.INV "NORM.INV()": {"#VALUE!", "NORM.INV requires 3 arguments"}, // NORMINV "NORMINV()": {"#VALUE!", "NORMINV requires 3 arguments"}, "NORMINV(\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMINV(0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMINV(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NORMINV(0,0,-1)": {"#N/A", "#N/A"}, "NORMINV(-1,0,0)": {"#N/A", "#N/A"}, "NORMINV(0,0,0)": {"#NUM!", "#NUM!"}, // NORM.S.DIST "NORM.S.DIST()": {"#VALUE!", "NORM.S.DIST requires 2 numeric arguments"}, // NORMSDIST "NORMSDIST()": {"#VALUE!", "NORMSDIST requires 1 numeric argument"}, // NORM.S.INV "NORM.S.INV()": {"#VALUE!", "NORM.S.INV requires 1 numeric argument"}, // NORMSINV "NORMSINV()": {"#VALUE!", "NORMSINV requires 1 numeric argument"}, // LARGE "LARGE()": {"#VALUE!", "LARGE requires 2 arguments"}, "LARGE(A1:A5,0)": {"#NUM!", "k should be > 0"}, "LARGE(A1:A5,6)": {"#NUM!", "k should be <= length of array"}, "LARGE(A1:A5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // MAX "MAX()": {"#VALUE!", "MAX requires at least 1 argument"}, "MAX(NA())": {"#N/A", "#N/A"}, // MAXA "MAXA()": {"#VALUE!", "MAXA requires at least 1 argument"}, "MAXA(NA())": {"#N/A", "#N/A"}, // MAXIFS "MAXIFS()": {"#VALUE!", "MAXIFS requires at least 3 arguments"}, "MAXIFS(F2:F4,A2:A4,\">0\",D2:D9)": {"#N/A", "#N/A"}, // MEDIAN "MEDIAN()": {"#VALUE!", "MEDIAN requires at least 1 argument"}, "MEDIAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MEDIAN(D1:D2)": {"#NUM!", "#NUM!"}, // MIN "MIN()": {"#VALUE!", "MIN requires at least 1 argument"}, "MIN(NA())": {"#N/A", "#N/A"}, // MINA "MINA()": {"#VALUE!", "MINA requires at least 1 argument"}, "MINA(NA())": {"#N/A", "#N/A"}, // MINIFS "MINIFS()": {"#VALUE!", "MINIFS requires at least 3 arguments"}, "MINIFS(F2:F4,A2:A4,\"<0\",D2:D9)": {"#N/A", "#N/A"}, // PEARSON "PEARSON()": {"#VALUE!", "PEARSON requires 2 arguments"}, "PEARSON(A1:A2,B1:B1)": {"#N/A", "#N/A"}, "PEARSON(A4,A4)": {"#DIV/0!", "#DIV/0!"}, // PERCENTILE.EXC "PERCENTILE.EXC()": {"#VALUE!", "PERCENTILE.EXC requires 2 arguments"}, "PERCENTILE.EXC(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTILE.EXC(A1:A4,-1)": {"#NUM!", "#NUM!"}, "PERCENTILE.EXC(A1:A4,0)": {"#NUM!", "#NUM!"}, "PERCENTILE.EXC(A1:A4,1)": {"#NUM!", "#NUM!"}, "PERCENTILE.EXC(NA(),0.5)": {"#NUM!", "#NUM!"}, // PERCENTILE.INC "PERCENTILE.INC()": {"#VALUE!", "PERCENTILE.INC requires 2 arguments"}, // PERCENTILE "PERCENTILE()": {"#VALUE!", "PERCENTILE requires 2 arguments"}, "PERCENTILE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTILE(0,-1)": {"#N/A", "#N/A"}, "PERCENTILE(NA(),1)": {"#N/A", "#N/A"}, // PERCENTRANK.EXC "PERCENTRANK.EXC()": {"#VALUE!", "PERCENTRANK.EXC requires 2 or 3 arguments"}, "PERCENTRANK.EXC(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK.EXC(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK.EXC(A1:B4,0,0)": {"#NUM!", "PERCENTRANK.EXC arguments significance should be > 1"}, "PERCENTRANK.EXC(A1:B4,6)": {"#N/A", "#N/A"}, "PERCENTRANK.EXC(NA(),1)": {"#N/A", "#N/A"}, // PERCENTRANK.INC "PERCENTRANK.INC()": {"#VALUE!", "PERCENTRANK.INC requires 2 or 3 arguments"}, "PERCENTRANK.INC(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK.INC(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK.INC(A1:B4,0,0)": {"#NUM!", "PERCENTRANK.INC arguments significance should be > 1"}, "PERCENTRANK.INC(A1:B4,6)": {"#N/A", "#N/A"}, "PERCENTRANK.INC(NA(),1)": {"#N/A", "#N/A"}, // PERCENTRANK "PERCENTRANK()": {"#VALUE!", "PERCENTRANK requires 2 or 3 arguments"}, "PERCENTRANK(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERCENTRANK(A1:B4,0,0)": {"#NUM!", "PERCENTRANK arguments significance should be > 1"}, "PERCENTRANK(A1:B4,6)": {"#N/A", "#N/A"}, "PERCENTRANK(NA(),1)": {"#N/A", "#N/A"}, // PERMUT "PERMUT()": {"#VALUE!", "PERMUT requires 2 numeric arguments"}, "PERMUT(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERMUT(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERMUT(6,8)": {"#N/A", "#N/A"}, // PERMUTATIONA "PERMUTATIONA()": {"#VALUE!", "PERMUTATIONA requires 2 numeric arguments"}, "PERMUTATIONA(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERMUTATIONA(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PERMUTATIONA(-1,0)": {"#N/A", "#N/A"}, "PERMUTATIONA(0,-1)": {"#N/A", "#N/A"}, // PHI "PHI()": {"#VALUE!", "PHI requires 1 argument"}, "PHI(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // QUARTILE "QUARTILE()": {"#VALUE!", "QUARTILE requires 2 arguments"}, "QUARTILE(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "QUARTILE(A1:A4,-1)": {"#NUM!", "#NUM!"}, "QUARTILE(A1:A4,5)": {"#NUM!", "#NUM!"}, // QUARTILE.EXC "QUARTILE.EXC()": {"#VALUE!", "QUARTILE.EXC requires 2 arguments"}, "QUARTILE.EXC(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "QUARTILE.EXC(A1:A4,0)": {"#NUM!", "#NUM!"}, "QUARTILE.EXC(A1:A4,4)": {"#NUM!", "#NUM!"}, // QUARTILE.INC "QUARTILE.INC()": {"#VALUE!", "QUARTILE.INC requires 2 arguments"}, // RANK "RANK()": {"#VALUE!", "RANK requires at least 2 arguments"}, "RANK(1,A1:B5,0,0)": {"#VALUE!", "RANK requires at most 3 arguments"}, "RANK(-1,A1:B5)": {"#N/A", "#N/A"}, "RANK(\"\",A1:B5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RANK(1,A1:B5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // RANK.EQ "RANK.EQ()": {"#VALUE!", "RANK.EQ requires at least 2 arguments"}, "RANK.EQ(1,A1:B5,0,0)": {"#VALUE!", "RANK.EQ requires at most 3 arguments"}, "RANK.EQ(-1,A1:B5)": {"#N/A", "#N/A"}, "RANK.EQ(\"\",A1:B5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RANK.EQ(1,A1:B5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // RSQ "RSQ()": {"#VALUE!", "RSQ requires 2 arguments"}, "RSQ(A1:A2,B1:B1)": {"#N/A", "#N/A"}, "RSQ(A4,A4)": {"#DIV/0!", "#DIV/0!"}, // SKEW "SKEW()": {"#VALUE!", "SKEW requires at least 1 argument"}, "SKEW(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SKEW(0)": {"#DIV/0!", "#DIV/0!"}, // SKEW.P "SKEW.P()": {"#VALUE!", "SKEW.P requires at least 1 argument"}, "SKEW.P(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SKEW.P(0)": {"#DIV/0!", "#DIV/0!"}, // SLOPE "SLOPE()": {"#VALUE!", "SLOPE requires 2 arguments"}, "SLOPE(A1:A2,B1:B1)": {"#N/A", "#N/A"}, "SLOPE(A4,A4)": {"#DIV/0!", "#DIV/0!"}, // SMALL "SMALL()": {"#VALUE!", "SMALL requires 2 arguments"}, "SMALL(A1:A5,0)": {"#NUM!", "k should be > 0"}, "SMALL(A1:A5,6)": {"#NUM!", "k should be <= length of array"}, "SMALL(A1:A5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // STANDARDIZE "STANDARDIZE()": {"#VALUE!", "STANDARDIZE requires 3 arguments"}, "STANDARDIZE(\"\",0,5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "STANDARDIZE(0,\"\",5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "STANDARDIZE(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "STANDARDIZE(0,0,0)": {"#N/A", "#N/A"}, // STDEVP "STDEVP()": {"#VALUE!", "STDEVP requires at least 1 argument"}, "STDEVP(\"\")": {"#DIV/0!", "#DIV/0!"}, // STDEV.P "STDEV.P()": {"#VALUE!", "STDEV.P requires at least 1 argument"}, "STDEV.P(\"\")": {"#DIV/0!", "#DIV/0!"}, // STDEVPA "STDEVPA()": {"#VALUE!", "STDEVPA requires at least 1 argument"}, "STDEVPA(\"\")": {"#DIV/0!", "#DIV/0!"}, // T.DIST "T.DIST()": {"#VALUE!", "T.DIST requires 3 arguments"}, "T.DIST(\"\",10,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST(1,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST(1,10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "T.DIST(1,0,TRUE)": {"#NUM!", "#NUM!"}, "T.DIST(1,-1,FALSE)": {"#NUM!", "#NUM!"}, "T.DIST(1,0,FALSE)": {"#DIV/0!", "#DIV/0!"}, // T.DIST.2T "T.DIST.2T()": {"#VALUE!", "T.DIST.2T requires 2 arguments"}, "T.DIST.2T(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST.2T(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST.2T(-1,10)": {"#NUM!", "#NUM!"}, "T.DIST.2T(1,0)": {"#NUM!", "#NUM!"}, // T.DIST.RT "T.DIST.RT()": {"#VALUE!", "T.DIST.RT requires 2 arguments"}, "T.DIST.RT(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST.RT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.DIST.RT(1,0)": {"#NUM!", "#NUM!"}, // TDIST "TDIST()": {"#VALUE!", "TDIST requires 3 arguments"}, "TDIST(\"\",10,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TDIST(1,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TDIST(1,10,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TDIST(-1,10,1)": {"#NUM!", "#NUM!"}, "TDIST(1,0,1)": {"#NUM!", "#NUM!"}, "TDIST(1,10,0)": {"#NUM!", "#NUM!"}, // T.INV "T.INV()": {"#VALUE!", "T.INV requires 2 arguments"}, "T.INV(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.INV(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.INV(0,10)": {"#NUM!", "#NUM!"}, "T.INV(1,10)": {"#NUM!", "#NUM!"}, "T.INV(0.25,0.5)": {"#NUM!", "#NUM!"}, // T.INV.2T "T.INV.2T()": {"#VALUE!", "T.INV.2T requires 2 arguments"}, "T.INV.2T(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.INV.2T(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "T.INV.2T(0,10)": {"#NUM!", "#NUM!"}, "T.INV.2T(0.25,0.5)": {"#NUM!", "#NUM!"}, // TINV "TINV()": {"#VALUE!", "TINV requires 2 arguments"}, "TINV(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TINV(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TINV(0,10)": {"#NUM!", "#NUM!"}, "TINV(0.25,0.5)": {"#NUM!", "#NUM!"}, // TRIMMEAN "TRIMMEAN()": {"#VALUE!", "TRIMMEAN requires 2 arguments"}, "TRIMMEAN(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TRIMMEAN(A1,1)": {"#NUM!", "#NUM!"}, "TRIMMEAN(A1,-1)": {"#NUM!", "#NUM!"}, // VAR "VAR()": {"#VALUE!", "VAR requires at least 1 argument"}, // VARA "VARA()": {"#VALUE!", "VARA requires at least 1 argument"}, // VARP "VARP()": {"#VALUE!", "VARP requires at least 1 argument"}, "VARP(\"\")": {"#DIV/0!", "#DIV/0!"}, // VAR.P "VAR.P()": {"#VALUE!", "VAR.P requires at least 1 argument"}, "VAR.P(\"\")": {"#DIV/0!", "#DIV/0!"}, // VAR.S "VAR.S()": {"#VALUE!", "VAR.S requires at least 1 argument"}, // VARPA "VARPA()": {"#VALUE!", "VARPA requires at least 1 argument"}, // WEIBULL "WEIBULL()": {"#VALUE!", "WEIBULL requires 4 arguments"}, "WEIBULL(\"\",1,1,FALSE)": {"#VALUE!", "#VALUE!"}, "WEIBULL(1,0,1,FALSE)": {"#N/A", "#N/A"}, "WEIBULL(1,1,-1,FALSE)": {"#N/A", "#N/A"}, // WEIBULL.DIST "WEIBULL.DIST()": {"#VALUE!", "WEIBULL.DIST requires 4 arguments"}, "WEIBULL.DIST(\"\",1,1,FALSE)": {"#VALUE!", "#VALUE!"}, "WEIBULL.DIST(1,0,1,FALSE)": {"#N/A", "#N/A"}, "WEIBULL.DIST(1,1,-1,FALSE)": {"#N/A", "#N/A"}, // Z.TEST "Z.TEST(A1)": {"#VALUE!", "Z.TEST requires at least 2 arguments"}, "Z.TEST(A1,0,0,0)": {"#VALUE!", "Z.TEST accepts at most 3 arguments"}, "Z.TEST(H1,0)": {"#N/A", "#N/A"}, "Z.TEST(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "Z.TEST(A1,1)": {"#DIV/0!", "#DIV/0!"}, "Z.TEST(A1,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ZTEST "ZTEST(A1)": {"#VALUE!", "ZTEST requires at least 2 arguments"}, "ZTEST(A1,0,0,0)": {"#VALUE!", "ZTEST accepts at most 3 arguments"}, "ZTEST(H1,0)": {"#N/A", "#N/A"}, "ZTEST(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ZTEST(A1,1)": {"#DIV/0!", "#DIV/0!"}, "ZTEST(A1,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // Information Functions // ERROR.TYPE "ERROR.TYPE()": {"#VALUE!", "ERROR.TYPE requires 1 argument"}, "ERROR.TYPE(1)": {"#N/A", "#N/A"}, // ISBLANK "ISBLANK(A1,A2)": {"#VALUE!", "ISBLANK requires 1 argument"}, // ISERR "ISERR()": {"#VALUE!", "ISERR requires 1 argument"}, // ISERROR "ISERROR()": {"#VALUE!", "ISERROR requires 1 argument"}, // ISEVEN "ISEVEN()": {"#VALUE!", "ISEVEN requires 1 argument"}, "ISEVEN(\"text\")": {"#VALUE!", "#VALUE!"}, "ISEVEN(A1:A2)": {"#VALUE!", "#VALUE!"}, // ISFORMULA "ISFORMULA()": {"#VALUE!", "ISFORMULA requires 1 argument"}, // ISLOGICAL "ISLOGICAL()": {"#VALUE!", "ISLOGICAL requires 1 argument"}, // ISNA "ISNA()": {"#VALUE!", "ISNA requires 1 argument"}, // ISNONTEXT "ISNONTEXT()": {"#VALUE!", "ISNONTEXT requires 1 argument"}, // ISNUMBER "ISNUMBER()": {"#VALUE!", "ISNUMBER requires 1 argument"}, // ISODD "ISODD()": {"#VALUE!", "ISODD requires 1 argument"}, "ISODD(\"text\")": {"#VALUE!", "#VALUE!"}, // ISREF "ISREF()": {"#VALUE!", "ISREF requires 1 argument"}, // ISTEXT "ISTEXT()": {"#VALUE!", "ISTEXT requires 1 argument"}, // N "N()": {"#VALUE!", "N requires 1 argument"}, "N(NA())": {"#N/A", "#N/A"}, // NA "NA()": {"#N/A", "#N/A"}, "NA(1)": {"#VALUE!", "NA accepts no arguments"}, // SHEET "SHEET(\"\",\"\")": {"#VALUE!", "SHEET accepts at most 1 argument"}, "SHEET(\"Sheet2\")": {"#N/A", "#N/A"}, // SHEETS "SHEETS(\"\",\"\")": {"#VALUE!", "SHEETS accepts at most 1 argument"}, "SHEETS(\"Sheet1\")": {"#N/A", "#N/A"}, // TYPE "TYPE()": {"#VALUE!", "TYPE requires 1 argument"}, // T "T()": {"#VALUE!", "T requires 1 argument"}, "T(NA())": {"#N/A", "#N/A"}, // Logical Functions // AND "AND(\"text\")": {"#VALUE!", "#VALUE!"}, "AND(A1:B1)": {"#VALUE!", "#VALUE!"}, "AND(\"1\",\"TRUE\",\"FALSE\")": {"#VALUE!", "#VALUE!"}, "AND()": {"#VALUE!", "AND requires at least 1 argument"}, "AND(1" + strings.Repeat(",1", 30) + ")": {"#VALUE!", "AND accepts at most 30 arguments"}, // FALSE "FALSE(A1)": {"#VALUE!", "FALSE takes no arguments"}, // IFERROR "IFERROR()": {"#VALUE!", "IFERROR requires 2 arguments"}, // IFNA "IFNA()": {"#VALUE!", "IFNA requires 2 arguments"}, // IFS "IFS()": {"#VALUE!", "IFS requires at least 2 arguments"}, "IFS(FALSE,FALSE)": {"#N/A", "#N/A"}, // NOT "NOT()": {"#VALUE!", "NOT requires 1 argument"}, "NOT(NOT())": {"#VALUE!", "NOT requires 1 argument"}, "NOT(\"\")": {"#VALUE!", "NOT expects 1 boolean or numeric argument"}, // OR "OR(\"text\")": {"#VALUE!", "#VALUE!"}, "OR(\"1\",\"TRUE\",\"FALSE\")": {"#VALUE!", "#VALUE!"}, "OR()": {"#VALUE!", "OR requires at least 1 argument"}, "OR(1" + strings.Repeat(",1", 30) + ")": {"#VALUE!", "OR accepts at most 30 arguments"}, // SWITCH "SWITCH()": {"#VALUE!", "SWITCH requires at least 3 arguments"}, "SWITCH(0,1,2)": {"#N/A", "#N/A"}, // TRUE "TRUE(A1)": {"#VALUE!", "TRUE takes no arguments"}, // XOR "XOR()": {"#VALUE!", "XOR requires at least 1 argument"}, "XOR(\"1\")": {"#VALUE!", "#VALUE!"}, "XOR(\"text\")": {"#VALUE!", "#VALUE!"}, "XOR(XOR(\"text\"))": {"#VALUE!", "#VALUE!"}, // Date and Time Functions // DATE "DATE()": {"#VALUE!", "DATE requires 3 number arguments"}, "DATE(\"text\",10,21)": {"#VALUE!", "DATE requires 3 number arguments"}, "DATE(2020,\"text\",21)": {"#VALUE!", "DATE requires 3 number arguments"}, "DATE(2020,10,\"text\")": {"#VALUE!", "DATE requires 3 number arguments"}, // DATEDIF "DATEDIF()": {"#VALUE!", "DATEDIF requires 3 number arguments"}, "DATEDIF(\"\",\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DATEDIF(43891,43101,\"Y\")": {"#NUM!", "start_date > end_date"}, "DATEDIF(43101,43891,\"x\")": {"#VALUE!", "DATEDIF has invalid unit"}, // DATEVALUE "DATEVALUE()": {"#VALUE!", "DATEVALUE requires 1 argument"}, "DATEVALUE(\"01/01\")": {"#VALUE!", "#VALUE!"}, // valid in Excel, which uses years by the system date "DATEVALUE(\"1900-0-0\")": {"#VALUE!", "#VALUE!"}, // DAY "DAY()": {"#VALUE!", "DAY requires exactly 1 argument"}, "DAY(-1)": {"#NUM!", "DAY only accepts positive argument"}, "DAY(0,0)": {"#VALUE!", "DAY requires exactly 1 argument"}, "DAY(\"text\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 9223372036854775808 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 9223372036854775808:00 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 00:9223372036854775808 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 9223372036854775808:00.0 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 0:1" + strings.Repeat("0", 309) + ".0 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 9223372036854775808:00:00 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 0:9223372036854775808:0 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 0:0:1" + strings.Repeat("0", 309) + " AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 0:61:0 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 0:00:60 AM\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 24:00:00\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 2020 00:00:10001\")": {"#VALUE!", "#VALUE!"}, "DAY(\"9223372036854775808/25/2020\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01/9223372036854775808/2020\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01/25/9223372036854775808\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01/25/10000\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01/25/100\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 9223372036854775808, 2020\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 9223372036854775808\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 10000\")": {"#VALUE!", "#VALUE!"}, "DAY(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, "DAY(\"9223372036854775808-25-2020\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01-9223372036854775808-2020\")": {"#VALUE!", "#VALUE!"}, "DAY(\"01-25-9223372036854775808\")": {"#VALUE!", "#VALUE!"}, "DAY(\"1900-0-0\")": {"#VALUE!", "#VALUE!"}, "DAY(\"14-25-1900\")": {"#VALUE!", "#VALUE!"}, "DAY(\"3-January-9223372036854775808\")": {"#VALUE!", "#VALUE!"}, "DAY(\"9223372036854775808-January-1900\")": {"#VALUE!", "#VALUE!"}, "DAY(\"0-January-1900\")": {"#VALUE!", "#VALUE!"}, // DAYS "DAYS()": {"#VALUE!", "DAYS requires 2 arguments"}, "DAYS(\"\",0)": {"#VALUE!", "#VALUE!"}, "DAYS(0,\"\")": {"#VALUE!", "#VALUE!"}, "DAYS(NA(),0)": {"#VALUE!", "#VALUE!"}, "DAYS(0,NA())": {"#VALUE!", "#VALUE!"}, // DAYS360 "DAYS360(\"12/12/1999\")": {"#VALUE!", "DAYS360 requires at least 2 arguments"}, "DAYS360(\"12/12/1999\", \"11/30/1999\",TRUE,\"\")": {"#VALUE!", "DAYS360 requires at most 3 arguments"}, "DAYS360(\"12/12/1999\", \"11/30/1999\",\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "DAYS360(\"12/12/1999\", \"\")": {"#VALUE!", "#VALUE!"}, "DAYS360(\"\", \"11/30/1999\")": {"#VALUE!", "#VALUE!"}, // EDATE "EDATE()": {"#VALUE!", "EDATE requires 2 arguments"}, "EDATE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EDATE(-1,0)": {"#NUM!", "#NUM!"}, "EDATE(\"\",0)": {"#VALUE!", "#VALUE!"}, "EDATE(\"January 25, 100\",0)": {"#VALUE!", "#VALUE!"}, // EOMONTH "EOMONTH()": {"#VALUE!", "EOMONTH requires 2 arguments"}, "EOMONTH(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EOMONTH(-1,0)": {"#NUM!", "#NUM!"}, "EOMONTH(\"\",0)": {"#VALUE!", "#VALUE!"}, "EOMONTH(\"January 25, 100\",0)": {"#VALUE!", "#VALUE!"}, // HOUR "HOUR()": {"#VALUE!", "HOUR requires exactly 1 argument"}, "HOUR(-1)": {"#NUM!", "HOUR only accepts positive argument"}, "HOUR(\"\")": {"#VALUE!", "#VALUE!"}, "HOUR(\"25:10:55\")": {"#VALUE!", "#VALUE!"}, // ISOWEEKNUM "ISOWEEKNUM()": {"#VALUE!", "ISOWEEKNUM requires 1 argument"}, "ISOWEEKNUM(\"\")": {"#VALUE!", "#VALUE!"}, "ISOWEEKNUM(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, "ISOWEEKNUM(-1)": {"#NUM!", "#NUM!"}, // MINUTE "MINUTE()": {"#VALUE!", "MINUTE requires exactly 1 argument"}, "MINUTE(-1)": {"#NUM!", "MINUTE only accepts positive argument"}, "MINUTE(\"\")": {"#VALUE!", "#VALUE!"}, "MINUTE(\"13:60:55\")": {"#VALUE!", "#VALUE!"}, // MONTH "MONTH()": {"#VALUE!", "MONTH requires exactly 1 argument"}, "MONTH(0,0)": {"#VALUE!", "MONTH requires exactly 1 argument"}, "MONTH(-1)": {"#NUM!", "MONTH only accepts positive argument"}, "MONTH(\"text\")": {"#VALUE!", "#VALUE!"}, "MONTH(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, // YEAR "YEAR()": {"#VALUE!", "YEAR requires exactly 1 argument"}, "YEAR(0,0)": {"#VALUE!", "YEAR requires exactly 1 argument"}, "YEAR(-1)": {"#NUM!", "YEAR only accepts positive argument"}, "YEAR(\"text\")": {"#VALUE!", "#VALUE!"}, "YEAR(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, // YEARFRAC "YEARFRAC()": {"#VALUE!", "YEARFRAC requires 3 or 4 arguments"}, "YEARFRAC(42005,42094,5)": {"#NUM!", "invalid basis"}, "YEARFRAC(\"\",42094,5)": {"#VALUE!", "#VALUE!"}, "YEARFRAC(42005,\"\",5)": {"#VALUE!", "#VALUE!"}, "YEARFRAC(42005,42094,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // NOW "NOW(A1)": {"#VALUE!", "NOW accepts no arguments"}, // SECOND "SECOND()": {"#VALUE!", "SECOND requires exactly 1 argument"}, "SECOND(-1)": {"#NUM!", "SECOND only accepts positive argument"}, "SECOND(\"\")": {"#VALUE!", "#VALUE!"}, "SECOND(\"25:55\")": {"#VALUE!", "#VALUE!"}, // TIME "TIME()": {"#VALUE!", "TIME requires 3 number arguments"}, "TIME(\"\",0,0)": {"#VALUE!", "TIME requires 3 number arguments"}, "TIME(0,0,-1)": {"#NUM!", "#NUM!"}, // TIMEVALUE "TIMEVALUE()": {"#VALUE!", "TIMEVALUE requires exactly 1 argument"}, "TIMEVALUE(1)": {"#VALUE!", "#VALUE!"}, "TIMEVALUE(-1)": {"#VALUE!", "#VALUE!"}, "TIMEVALUE(\"25:55\")": {"#VALUE!", "#VALUE!"}, // TODAY "TODAY(A1)": {"#VALUE!", "TODAY accepts no arguments"}, // WEEKDAY "WEEKDAY()": {"#VALUE!", "WEEKDAY requires at least 1 argument"}, "WEEKDAY(0,1,0)": {"#VALUE!", "WEEKDAY allows at most 2 arguments"}, "WEEKDAY(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "WEEKDAY(\"\",1)": {"#VALUE!", "#VALUE!"}, "WEEKDAY(0,0)": {"#VALUE!", "#VALUE!"}, "WEEKDAY(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, "WEEKDAY(-1,1)": {"#NUM!", "#NUM!"}, // WEEKNUM "WEEKNUM()": {"#VALUE!", "WEEKNUM requires at least 1 argument"}, "WEEKNUM(0,1,0)": {"#VALUE!", "WEEKNUM allows at most 2 arguments"}, "WEEKNUM(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "WEEKNUM(\"\",1)": {"#VALUE!", "#VALUE!"}, "WEEKNUM(\"January 25, 100\")": {"#VALUE!", "#VALUE!"}, "WEEKNUM(0,0)": {"#NUM!", "#NUM!"}, "WEEKNUM(-1,1)": {"#NUM!", "#NUM!"}, // Text Functions // ARRAYTOTEXT "ARRAYTOTEXT()": {"#VALUE!", "ARRAYTOTEXT requires at least 1 argument"}, "ARRAYTOTEXT(A1,0,0)": {"#VALUE!", "ARRAYTOTEXT allows at most 2 arguments"}, "ARRAYTOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ARRAYTOTEXT(A1,2)": {"#VALUE!", "#VALUE!"}, // BAHTTEXT "BAHTTEXT()": {"#VALUE!", "BAHTTEXT requires 1 numeric argument"}, "BAHTTEXT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // CHAR "CHAR()": {"#VALUE!", "CHAR requires 1 argument"}, "CHAR(-1)": {"#VALUE!", "#VALUE!"}, "CHAR(256)": {"#VALUE!", "#VALUE!"}, "CHAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // CLEAN "CLEAN()": {"#VALUE!", "CLEAN requires 1 argument"}, "CLEAN(1,2)": {"#VALUE!", "CLEAN requires 1 argument"}, // CODE "CODE()": {"#VALUE!", "CODE requires 1 argument"}, "CODE(1,2)": {"#VALUE!", "CODE requires 1 argument"}, // CONCAT "CONCAT(NA())": {"#N/A", "#N/A"}, "CONCAT(1,1/0)": {"#DIV/0!", "#DIV/0!"}, // CONCATENATE "CONCATENATE(NA())": {"#N/A", "#N/A"}, "CONCATENATE(1,1/0)": {"#DIV/0!", "#DIV/0!"}, // DBCS "DBCS(NA())": {"#N/A", "#N/A"}, "DBCS()": {"#VALUE!", "DBCS requires 1 argument"}, // EXACT "EXACT()": {"#VALUE!", "EXACT requires 2 arguments"}, "EXACT(1,2,3)": {"#VALUE!", "EXACT requires 2 arguments"}, // FIXED "FIXED()": {"#VALUE!", "FIXED requires at least 1 argument"}, "FIXED(0,1,2,3)": {"#VALUE!", "FIXED allows at most 3 arguments"}, "FIXED(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FIXED(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FIXED(0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, // FIND "FIND()": {"#VALUE!", "FIND requires at least 2 arguments"}, "FIND(1,2,3,4)": {"#VALUE!", "FIND allows at most 3 arguments"}, "FIND(\"x\",\"\")": {"#VALUE!", "#VALUE!"}, "FIND(\"x\",\"x\",-1)": {"#VALUE!", "#VALUE!"}, "FIND(\"x\",\"x\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // FINDB "FINDB()": {"#VALUE!", "FINDB requires at least 2 arguments"}, "FINDB(1,2,3,4)": {"#VALUE!", "FINDB allows at most 3 arguments"}, "FINDB(\"x\",\"\")": {"#VALUE!", "#VALUE!"}, "FINDB(\"x\",\"x\",-1)": {"#VALUE!", "#VALUE!"}, "FINDB(\"x\",\"x\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // LEFT "LEFT()": {"#VALUE!", "LEFT requires at least 1 argument"}, "LEFT(\"\",2,3)": {"#VALUE!", "LEFT allows at most 2 arguments"}, "LEFT(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LEFT(\"\",-1)": {"#VALUE!", "#VALUE!"}, // LEFTB "LEFTB()": {"#VALUE!", "LEFTB requires at least 1 argument"}, "LEFTB(\"\",2,3)": {"#VALUE!", "LEFTB allows at most 2 arguments"}, "LEFTB(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "LEFTB(\"\",-1)": {"#VALUE!", "#VALUE!"}, // LEN "LEN()": {"#VALUE!", "LEN requires 1 string argument"}, // LENB "LENB()": {"#VALUE!", "LENB requires 1 string argument"}, // LOWER "LOWER()": {"#VALUE!", "LOWER requires 1 argument"}, "LOWER(1,2)": {"#VALUE!", "LOWER requires 1 argument"}, // MID "MID()": {"#VALUE!", "MID requires 3 arguments"}, "MID(\"\",0,1)": {"#VALUE!", "#VALUE!"}, "MID(\"\",1,-1)": {"#VALUE!", "#VALUE!"}, "MID(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MID(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // MIDB "MIDB()": {"#VALUE!", "MIDB requires 3 arguments"}, "MIDB(\"\",0,1)": {"#VALUE!", "#VALUE!"}, "MIDB(\"\",1,-1)": {"#VALUE!", "#VALUE!"}, "MIDB(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MIDB(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // PROPER "PROPER()": {"#VALUE!", "PROPER requires 1 argument"}, "PROPER(1,2)": {"#VALUE!", "PROPER requires 1 argument"}, // REPLACE "REPLACE()": {"#VALUE!", "REPLACE requires 4 arguments"}, "REPLACE(\"text\",0,4,\"string\")": {"#VALUE!", "#VALUE!"}, "REPLACE(\"text\",\"\",0,\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "REPLACE(\"text\",1,\"\",\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // REPLACEB "REPLACEB()": {"#VALUE!", "REPLACEB requires 4 arguments"}, "REPLACEB(\"text\",0,4,\"string\")": {"#VALUE!", "#VALUE!"}, "REPLACEB(\"text\",\"\",0,\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "REPLACEB(\"text\",1,\"\",\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // REPT "REPT()": {"#VALUE!", "REPT requires 2 arguments"}, "REPT(INT(0),2)": {"#VALUE!", "REPT requires first argument to be a string"}, "REPT(\"*\",\"*\")": {"#VALUE!", "REPT requires second argument to be a number"}, "REPT(\"*\",-1)": {"#VALUE!", "REPT requires second argument to be >= 0"}, // RIGHT "RIGHT()": {"#VALUE!", "RIGHT requires at least 1 argument"}, "RIGHT(\"\",2,3)": {"#VALUE!", "RIGHT allows at most 2 arguments"}, "RIGHT(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RIGHT(\"\",-1)": {"#VALUE!", "#VALUE!"}, // RIGHTB "RIGHTB()": {"#VALUE!", "RIGHTB requires at least 1 argument"}, "RIGHTB(\"\",2,3)": {"#VALUE!", "RIGHTB allows at most 2 arguments"}, "RIGHTB(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RIGHTB(\"\",-1)": {"#VALUE!", "#VALUE!"}, // SUBSTITUTE "SUBSTITUTE()": {"#VALUE!", "SUBSTITUTE requires 3 or 4 arguments"}, "SUBSTITUTE(\"\",\"\",\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "SUBSTITUTE(\"\",\"\",\"\",0)": {"#VALUE!", "instance_num should be > 0"}, // TEXT "TEXT()": {"#VALUE!", "TEXT requires 2 arguments"}, "TEXT(NA(),\"\")": {"#N/A", "#N/A"}, "TEXT(0,NA())": {"#N/A", "#N/A"}, // TEXTAFTER "TEXTAFTER()": {"#VALUE!", "TEXTAFTER requires at least 2 arguments"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,0,0,\"\",0)": {"#VALUE!", "TEXTAFTER accepts at most 6 arguments"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "TEXTAFTER(\"\",\"hood\")": {"#N/A", "#N/A"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",0)": {"#VALUE!", "#VALUE!"}, "TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",28)": {"#VALUE!", "#VALUE!"}, // TEXTBEFORE "TEXTBEFORE()": {"#VALUE!", "TEXTBEFORE requires at least 2 arguments"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,0,0,\"\",0)": {"#VALUE!", "TEXTBEFORE accepts at most 6 arguments"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "TEXTBEFORE(\"\",\"hood\")": {"#N/A", "#N/A"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",0)": {"#VALUE!", "#VALUE!"}, "TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",28)": {"#VALUE!", "#VALUE!"}, // TEXTJOIN "TEXTJOIN()": {"#VALUE!", "TEXTJOIN requires at least 3 arguments"}, "TEXTJOIN(\"\",\"\",1)": {"#VALUE!", "#VALUE!"}, "TEXTJOIN(\"\",TRUE,NA())": {"#N/A", "#N/A"}, "TEXTJOIN(\"\",TRUE," + strings.Repeat("0,", 250) + ",0)": {"#VALUE!", "TEXTJOIN accepts at most 252 arguments"}, "TEXTJOIN(\",\",FALSE,REPT(\"*\",32768))": {"#VALUE!", "TEXTJOIN function exceeds 32767 characters"}, "TEXTJOIN(\"\",FALSE,REPT(\"\U0001F600\",16384))": {"#VALUE!", "TEXTJOIN function exceeds 32767 characters"}, // TRIM "TRIM()": {"#VALUE!", "TRIM requires 1 argument"}, "TRIM(1,2)": {"#VALUE!", "TRIM requires 1 argument"}, // UNICHAR "UNICHAR()": {"#VALUE!", "UNICHAR requires 1 argument"}, "UNICHAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "UNICHAR(55296)": {"#VALUE!", "#VALUE!"}, "UNICHAR(0)": {"#VALUE!", "#VALUE!"}, // UNICODE "UNICODE()": {"#VALUE!", "UNICODE requires 1 argument"}, "UNICODE(\"\")": {"#VALUE!", "#VALUE!"}, // VALUE "VALUE()": {"#VALUE!", "VALUE requires 1 argument"}, "VALUE(\"\")": {"#VALUE!", "#VALUE!"}, // VALUETOTEXT "VALUETOTEXT()": {"#VALUE!", "VALUETOTEXT requires at least 1 argument"}, "VALUETOTEXT(A1,0,0)": {"#VALUE!", "VALUETOTEXT allows at most 2 arguments"}, "VALUETOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VALUETOTEXT(A1,2)": {"#VALUE!", "#VALUE!"}, // UPPER "UPPER()": {"#VALUE!", "UPPER requires 1 argument"}, "UPPER(1,2)": {"#VALUE!", "UPPER requires 1 argument"}, // Conditional Functions // IF "IF()": {"#VALUE!", "IF requires at least 1 argument"}, "IF(0,1,2,3)": {"#VALUE!", "IF accepts at most 3 arguments"}, "IF(D1,1,2)": {"#VALUE!", "strconv.ParseBool: parsing \"Month\": invalid syntax"}, // Excel Lookup and Reference Functions // ADDRESS "ADDRESS()": {"#VALUE!", "ADDRESS requires at least 2 arguments"}, "ADDRESS(1,1,1,TRUE,\"Sheet1\",0)": {"#VALUE!", "ADDRESS requires at most 5 arguments"}, "ADDRESS(\"\",1,1,TRUE)": {"#VALUE!", "#VALUE!"}, "ADDRESS(1,\"\",1,TRUE)": {"#VALUE!", "#VALUE!"}, "ADDRESS(1,1,\"\",TRUE)": {"#VALUE!", "#VALUE!"}, "ADDRESS(1,1,1,\"\")": {"#VALUE!", "#VALUE!"}, "ADDRESS(1,1,0,TRUE)": {"#NUM!", "#NUM!"}, "ADDRESS(1,16385,2,TRUE)": {"#VALUE!", "#VALUE!"}, "ADDRESS(1,16385,3,TRUE)": {"#VALUE!", "#VALUE!"}, "ADDRESS(1048577,1,1,TRUE)": {"#VALUE!", "#VALUE!"}, // CHOOSE "CHOOSE()": {"#VALUE!", "CHOOSE requires 2 arguments"}, "CHOOSE(\"index_num\",0)": {"#VALUE!", "CHOOSE requires first argument of type number"}, "CHOOSE(2,0)": {"#VALUE!", "index_num should be <= to the number of values"}, "CHOOSE(1,NA())": {"#N/A", "#N/A"}, // COLUMN "COLUMN(1,2)": {"#VALUE!", "COLUMN requires at most 1 argument"}, "COLUMN(\"\")": {"#VALUE!", "invalid reference"}, "COLUMN(Sheet1)": {"#NAME?", "invalid reference"}, "COLUMN(Sheet1!A1!B1)": {"#NAME?", "invalid reference"}, "COLUMN(Sheet1!A1:Sheet2!A2)": {"#NAME?", "invalid reference"}, "COLUMN(Sheet1!A1:1A)": {"#NAME?", "invalid reference"}, // COLUMNS "COLUMNS()": {"#VALUE!", "COLUMNS requires 1 argument"}, "COLUMNS(1)": {"#VALUE!", "invalid reference"}, "COLUMNS(\"\")": {"#VALUE!", "invalid reference"}, "COLUMNS(Sheet1)": {"#NAME?", "invalid reference"}, "COLUMNS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"}, "COLUMNS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"}, // FORMULATEXT "FORMULATEXT()": {"#VALUE!", "FORMULATEXT requires 1 argument"}, "FORMULATEXT(1)": {"#VALUE!", "#VALUE!"}, // HLOOKUP "HLOOKUP()": {"#VALUE!", "HLOOKUP requires at least 3 arguments"}, "HLOOKUP(D2,D1,1,FALSE)": {"#VALUE!", "HLOOKUP requires second argument of table array"}, "HLOOKUP(D2,D:D,FALSE,FALSE)": {"#VALUE!", "HLOOKUP requires numeric row argument"}, "HLOOKUP(D2,D:D,1,FALSE,FALSE)": {"#VALUE!", "HLOOKUP requires at most 4 arguments"}, "HLOOKUP(D2,D:D,1,2)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(D2,D10:D10,1,FALSE)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(D2,D2:D3,4,FALSE)": {"#N/A", "HLOOKUP has invalid row index"}, "HLOOKUP(D2,C:C,1,FALSE)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(ISNUMBER(1),F3:F9,1)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(INT(1),E2:E9,1)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(MUNIT(2),MUNIT(3),1)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(A1:B2,B2:B3,1)": {"#N/A", "HLOOKUP no result found"}, // MATCH "MATCH()": {"#VALUE!", "MATCH requires 1 or 2 arguments"}, "MATCH(0,A1:A1,0,0)": {"#VALUE!", "MATCH requires 1 or 2 arguments"}, "MATCH(0,A1:A1,\"x\")": {"#VALUE!", "MATCH requires numeric match_type argument"}, "MATCH(0,A1)": {"#N/A", "MATCH arguments lookup_array should be one-dimensional array"}, "MATCH(0,A1:B2)": {"#N/A", "MATCH arguments lookup_array should be one-dimensional array"}, "MATCH(0,A1:B1)": {"#N/A", "#N/A"}, // TRANSPOSE "TRANSPOSE()": {"#VALUE!", "TRANSPOSE requires 1 argument"}, // HYPERLINK "HYPERLINK()": {"#VALUE!", "HYPERLINK requires at least 1 argument"}, "HYPERLINK(\"https://github.com/xuri/excelize\",\"Excelize\",\"\")": {"#VALUE!", "HYPERLINK allows at most 2 arguments"}, // VLOOKUP "VLOOKUP()": {"#VALUE!", "VLOOKUP requires at least 3 arguments"}, "VLOOKUP(D2,D1,1,FALSE)": {"#VALUE!", "VLOOKUP requires second argument of table array"}, "VLOOKUP(D2,D:D,FALSE,FALSE)": {"#VALUE!", "VLOOKUP requires numeric col argument"}, "VLOOKUP(D2,D:D,1,FALSE,FALSE)": {"#VALUE!", "VLOOKUP requires at most 4 arguments"}, "VLOOKUP(D2,D10:D10,1,FALSE)": {"#N/A", "VLOOKUP no result found"}, "VLOOKUP(D2,D:D,2,FALSE)": {"#N/A", "VLOOKUP has invalid column index"}, "VLOOKUP(D2,C:C,1,FALSE)": {"#N/A", "VLOOKUP no result found"}, "VLOOKUP(ISNUMBER(1),F3:F9,1)": {"#N/A", "VLOOKUP no result found"}, "VLOOKUP(INT(1),E2:E9,1)": {"#N/A", "VLOOKUP no result found"}, "VLOOKUP(MUNIT(2),MUNIT(3),1)": {"#N/A", "VLOOKUP no result found"}, "VLOOKUP(1,G1:H2,1,FALSE)": {"#N/A", "VLOOKUP no result found"}, // INDEX "INDEX()": {"#VALUE!", "INDEX requires 2 or 3 arguments"}, "INDEX(A1,2)": {"#REF!", "INDEX row_num out of range"}, "INDEX(A1,0,2)": {"#REF!", "INDEX col_num out of range"}, "INDEX(A1:A1,2)": {"#REF!", "INDEX row_num out of range"}, "INDEX(A1:A1,0,2)": {"#REF!", "INDEX col_num out of range"}, "INDEX(A1:B2,2,3)": {"#REF!", "INDEX col_num out of range"}, "INDEX(A1:A2,0,0)": {"#VALUE!", "#VALUE!"}, "INDEX(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "INDEX(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // INDIRECT "INDIRECT()": {"#VALUE!", "INDIRECT requires 1 or 2 arguments"}, "INDIRECT(\"E\"&1,TRUE,1)": {"#VALUE!", "INDIRECT requires 1 or 2 arguments"}, "INDIRECT(\"R1048577C1\",\"\")": {"#VALUE!", "#VALUE!"}, "INDIRECT(\"E1048577\")": {"#REF!", "#REF!"}, "INDIRECT(\"R1048577C1\",FALSE)": {"#REF!", "#REF!"}, "INDIRECT(\"R1C16385\",FALSE)": {"#REF!", "#REF!"}, "INDIRECT(\"\",FALSE)": {"#REF!", "#REF!"}, "INDIRECT(\"R C1\",FALSE)": {"#REF!", "#REF!"}, "INDIRECT(\"R1C \",FALSE)": {"#REF!", "#REF!"}, "INDIRECT(\"R1C1:R2C \",FALSE)": {"#REF!", "#REF!"}, // LOOKUP "LOOKUP()": {"#VALUE!", "LOOKUP requires at least 2 arguments"}, "LOOKUP(D2,D1,D2)": {"#VALUE!", "LOOKUP requires second argument of table array"}, "LOOKUP(D2,D1,D2,FALSE)": {"#VALUE!", "LOOKUP requires at most 3 arguments"}, "LOOKUP(1,MUNIT(0))": {"#VALUE!", "LOOKUP requires not empty range as second argument"}, "LOOKUP(D1,MUNIT(1),MUNIT(1))": {"#N/A", "LOOKUP no result found"}, // ROW "ROW(1,2)": {"#VALUE!", "ROW requires at most 1 argument"}, "ROW(\"\")": {"#VALUE!", "invalid reference"}, "ROW(Sheet1)": {"#NAME?", "invalid reference"}, "ROW(Sheet1!A1!B1)": {"#NAME?", "invalid reference"}, // ROWS "ROWS()": {"#VALUE!", "ROWS requires 1 argument"}, "ROWS(1)": {"#VALUE!", "invalid reference"}, "ROWS(\"\")": {"#VALUE!", "invalid reference"}, "ROWS(Sheet1)": {"#NAME?", "invalid reference"}, "ROWS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"}, "ROWS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"}, // Web Functions // ENCODEURL "ENCODEURL()": {"#VALUE!", "ENCODEURL requires 1 argument"}, // Financial Functions // ACCRINT "ACCRINT()": {"#VALUE!", "ACCRINT requires at least 6 arguments"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,FALSE,0)": {"#VALUE!", "ACCRINT allows at most 8 arguments"}, "ACCRINT(\"\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"}, "ACCRINT(\"01/01/2012\",\"\",\"12/31/2013\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",\"\",10000,4,1,FALSE)": {"#NUM!", "#NUM!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,\"\",4,1,FALSE)": {"#NUM!", "#NUM!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,3)": {"#NUM!", "#NUM!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,\"\",1,FALSE)": {"#NUM!", "#NUM!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,\"\",FALSE)": {"#NUM!", "#NUM!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,\"\")": {"#VALUE!", "#VALUE!"}, "ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,5,FALSE)": {"#NUM!", "invalid basis"}, // ACCRINTM "ACCRINTM()": {"#VALUE!", "ACCRINTM requires 4 or 5 arguments"}, "ACCRINTM(\"\",\"01/01/2012\",8%,10000)": {"#VALUE!", "#VALUE!"}, "ACCRINTM(\"01/01/2012\",\"\",8%,10000)": {"#VALUE!", "#VALUE!"}, "ACCRINTM(\"12/31/2012\",\"01/01/2012\",8%,10000)": {"#NUM!", "#NUM!"}, "ACCRINTM(\"01/01/2012\",\"12/31/2012\",\"\",10000)": {"#NUM!", "#NUM!"}, "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,\"\",10000)": {"#NUM!", "#NUM!"}, "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,-1,10000)": {"#NUM!", "#NUM!"}, "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,\"\")": {"#NUM!", "#NUM!"}, "ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,5)": {"#NUM!", "invalid basis"}, // AMORDEGRC "AMORDEGRC()": {"#VALUE!", "AMORDEGRC requires 6 or 7 arguments"}, "AMORDEGRC(\"\",\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORDEGRC requires cost to be number argument"}, "AMORDEGRC(-1,\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORDEGRC requires cost >= 0"}, "AMORDEGRC(150,\"\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "#VALUE!"}, "AMORDEGRC(150,\"01/01/2015\",\"\",20,1,20%)": {"#VALUE!", "#VALUE!"}, "AMORDEGRC(150,\"09/30/2015\",\"01/01/2015\",20,1,20%)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",\"\",1,20%)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",-1,1,20%)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,\"\",20%)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,-1,20%)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,\"\")": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,-1)": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,\"\")": {"#NUM!", "#NUM!"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,50%)": {"#NUM!", "AMORDEGRC requires rate to be < 0.5"}, "AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,5)": {"#NUM!", "invalid basis"}, // AMORLINC "AMORLINC()": {"#VALUE!", "AMORLINC requires 6 or 7 arguments"}, "AMORLINC(\"\",\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORLINC requires cost to be number argument"}, "AMORLINC(-1,\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORLINC requires cost >= 0"}, "AMORLINC(150,\"\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "#VALUE!"}, "AMORLINC(150,\"01/01/2015\",\"\",20,1,20%)": {"#VALUE!", "#VALUE!"}, "AMORLINC(150,\"09/30/2015\",\"01/01/2015\",20,1,20%)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",\"\",1,20%)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",-1,1,20%)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,\"\",20%)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,-1,20%)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,\"\")": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,-1)": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,\"\")": {"#NUM!", "#NUM!"}, "AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,5)": {"#NUM!", "invalid basis"}, // COUPDAYBS "COUPDAYBS()": {"#VALUE!", "COUPDAYBS requires 3 or 4 arguments"}, "COUPDAYBS(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYBS(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYBS(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPDAYBS(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYBS requires maturity > settlement"}, // COUPDAYS "COUPDAYS()": {"#VALUE!", "COUPDAYS requires 3 or 4 arguments"}, "COUPDAYS(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYS(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYS(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPDAYS(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYS requires maturity > settlement"}, // COUPDAYSNC "COUPDAYSNC()": {"#VALUE!", "COUPDAYSNC requires 3 or 4 arguments"}, "COUPDAYSNC(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYSNC(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPDAYSNC(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYSNC requires maturity > settlement"}, // COUPNCD "COUPNCD()": {"#VALUE!", "COUPNCD requires 3 or 4 arguments"}, "COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPNCD requires 3 or 4 arguments"}, "COUPNCD(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPNCD(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPNCD(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPNCD(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPNCD(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"}, "COUPNCD(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPNCD requires maturity > settlement"}, // COUPNUM "COUPNUM()": {"#VALUE!", "COUPNUM requires 3 or 4 arguments"}, "COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPNUM requires 3 or 4 arguments"}, "COUPNUM(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPNUM(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPNUM(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPNUM(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPNUM(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"}, "COUPNUM(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPNUM requires maturity > settlement"}, // COUPPCD "COUPPCD()": {"#VALUE!", "COUPPCD requires 3 or 4 arguments"}, "COUPPCD(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPPCD requires 3 or 4 arguments"}, "COUPPCD(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"}, "COUPPCD(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"}, "COUPPCD(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"}, "COUPPCD(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"}, "COUPPCD(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"}, "COUPPCD(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPPCD requires maturity > settlement"}, // CUMIPMT "CUMIPMT()": {"#VALUE!", "CUMIPMT requires 6 arguments"}, "CUMIPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"}, "CUMIPMT(0,0,0,-1,0,0)": {"#N/A", "#N/A"}, "CUMIPMT(0,0,0,1,0,0)": {"#N/A", "#N/A"}, "CUMIPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMIPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMIPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMIPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMIPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMIPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // CUMPRINC "CUMPRINC()": {"#VALUE!", "CUMPRINC requires 6 arguments"}, "CUMPRINC(0,0,0,0,0,2)": {"#N/A", "#N/A"}, "CUMPRINC(0,0,0,-1,0,0)": {"#N/A", "#N/A"}, "CUMPRINC(0,0,0,1,0,0)": {"#N/A", "#N/A"}, "CUMPRINC(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMPRINC(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMPRINC(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMPRINC(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMPRINC(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "CUMPRINC(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // DB "DB()": {"#VALUE!", "DB requires at least 4 arguments"}, "DB(0,0,0,0,0,0)": {"#VALUE!", "DB allows at most 5 arguments"}, "DB(-1,0,0,0)": {"#N/A", "#N/A"}, "DB(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DB(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DB(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DB(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DB(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // DDB "DDB()": {"#VALUE!", "DDB requires at least 4 arguments"}, "DDB(0,0,0,0,0,0)": {"#VALUE!", "DDB allows at most 5 arguments"}, "DDB(-1,0,0,0)": {"#N/A", "#N/A"}, "DDB(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DDB(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DDB(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DDB(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DDB(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // DISC "DISC()": {"#VALUE!", "DISC requires 4 or 5 arguments"}, "DISC(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"}, "DISC(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"}, "DISC(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"}, "DISC(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"}, "DISC(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"}, "DISC(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "DISC requires maturity > settlement"}, "DISC(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "DISC requires pr > 0"}, "DISC(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "DISC requires redemption > 0"}, "DISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"}, // DOLLAR "DOLLAR()": {"#VALUE!", "DOLLAR requires at least 1 argument"}, "DOLLAR(0,0,0)": {"#VALUE!", "DOLLAR requires 1 or 2 arguments"}, "DOLLAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLAR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLAR(1,200)": {"#VALUE!", "decimal value should be less than 128"}, // DOLLARDE "DOLLARDE()": {"#VALUE!", "DOLLARDE requires 2 arguments"}, "DOLLARDE(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLARDE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLARDE(0,-1)": {"#NUM!", "#NUM!"}, "DOLLARDE(0,0)": {"#DIV/0!", "#DIV/0!"}, // DOLLARFR "DOLLARFR()": {"#VALUE!", "DOLLARFR requires 2 arguments"}, "DOLLARFR(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLARFR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DOLLARFR(0,-1)": {"#NUM!", "#NUM!"}, "DOLLARFR(0,0)": {"#DIV/0!", "#DIV/0!"}, // DURATION "DURATION()": {"#VALUE!", "DURATION requires 5 or 6 arguments"}, "DURATION(\"\",\"03/31/2025\",10%,8%,4)": {"#VALUE!", "#VALUE!"}, "DURATION(\"04/01/2015\",\"\",10%,8%,4)": {"#VALUE!", "#VALUE!"}, "DURATION(\"03/31/2025\",\"04/01/2015\",10%,8%,4)": {"#NUM!", "DURATION requires maturity > settlement"}, "DURATION(\"04/01/2015\",\"03/31/2025\",-1,8%,4)": {"#NUM!", "DURATION requires coupon >= 0"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,-1,4)": {"#NUM!", "DURATION requires yld >= 0"}, "DURATION(\"04/01/2015\",\"03/31/2025\",\"\",8%,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,3)": {"#NUM!", "#NUM!"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,\"\")": {"#NUM!", "#NUM!"}, "DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,5)": {"#NUM!", "invalid basis"}, // EFFECT "EFFECT()": {"#VALUE!", "EFFECT requires 2 arguments"}, "EFFECT(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EFFECT(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EFFECT(0,0)": {"#NUM!", "#NUM!"}, "EFFECT(1,0)": {"#NUM!", "#NUM!"}, // EUROCONVERT "EUROCONVERT()": {"#VALUE!", "EUROCONVERT requires at least 3 arguments"}, "EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,3,1)": {"#VALUE!", "EUROCONVERT allows at most 5 arguments"}, "EUROCONVERT(\"\",\"FRF\",\"DEM\",TRUE,3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EUROCONVERT(1.47,\"FRF\",\"DEM\",\"\",3)": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "EUROCONVERT(1.47,\"\",\"DEM\")": {"#VALUE!", "#VALUE!"}, "EUROCONVERT(1.47,\"FRF\",\"\",TRUE,3)": {"#VALUE!", "#VALUE!"}, // FV "FV()": {"#VALUE!", "FV requires at least 3 arguments"}, "FV(0,0,0,0,0,0,0)": {"#VALUE!", "FV allows at most 5 arguments"}, "FV(0,0,0,0,2)": {"#N/A", "#N/A"}, "FV(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FV(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FV(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FV(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FV(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // FVSCHEDULE "FVSCHEDULE()": {"#VALUE!", "FVSCHEDULE requires 2 arguments"}, "FVSCHEDULE(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "FVSCHEDULE(0,\"x\")": {"#VALUE!", "strconv.ParseFloat: parsing \"x\": invalid syntax"}, // INTRATE "INTRATE()": {"#VALUE!", "INTRATE requires 4 or 5 arguments"}, "INTRATE(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"}, "INTRATE(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"}, "INTRATE(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "INTRATE requires maturity > settlement"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "INTRATE requires investment > 0"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "INTRATE requires redemption > 0"}, "INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"}, // IPMT "IPMT()": {"#VALUE!", "IPMT requires at least 4 arguments"}, "IPMT(0,0,0,0,0,0,0)": {"#VALUE!", "IPMT allows at most 6 arguments"}, "IPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"}, "IPMT(0,-1,0,0,0,0)": {"#N/A", "#N/A"}, "IPMT(0,1,0,0,0,0)": {"#N/A", "#N/A"}, "IPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ISPMT "ISPMT()": {"#VALUE!", "ISPMT requires 4 arguments"}, "ISPMT(\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ISPMT(0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ISPMT(0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ISPMT(0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // MDURATION "MDURATION()": {"#VALUE!", "MDURATION requires 5 or 6 arguments"}, "MDURATION(\"\",\"03/31/2025\",10%,8%,4)": {"#VALUE!", "#VALUE!"}, "MDURATION(\"04/01/2015\",\"\",10%,8%,4)": {"#VALUE!", "#VALUE!"}, "MDURATION(\"03/31/2025\",\"04/01/2015\",10%,8%,4)": {"#NUM!", "MDURATION requires maturity > settlement"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",-1,8%,4)": {"#NUM!", "MDURATION requires coupon >= 0"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,-1,4)": {"#NUM!", "MDURATION requires yld >= 0"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",\"\",8%,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,3)": {"#NUM!", "#NUM!"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,\"\")": {"#NUM!", "#NUM!"}, "MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,5)": {"#NUM!", "invalid basis"}, // NOMINAL "NOMINAL()": {"#VALUE!", "NOMINAL requires 2 arguments"}, "NOMINAL(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NOMINAL(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NOMINAL(0,0)": {"#NUM!", "#NUM!"}, "NOMINAL(1,0)": {"#NUM!", "#NUM!"}, // NPER "NPER()": {"#VALUE!", "NPER requires at least 3 arguments"}, "NPER(0,0,0,0,0,0)": {"#VALUE!", "NPER allows at most 5 arguments"}, "NPER(0,0,0)": {"#NUM!", "#NUM!"}, "NPER(0,0,0,0,2)": {"#N/A", "#N/A"}, "NPER(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NPER(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NPER(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NPER(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "NPER(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // NPV "NPV()": {"#VALUE!", "NPV requires at least 2 arguments"}, "NPV(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // ODDFPRICE "ODDFPRICE()": {"#VALUE!", "ODDFPRICE requires 8 or 9 arguments"}, "ODDFPRICE(\"\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFPRICE(\"02/01/2017\",\"\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"02/01/2017\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires settlement > issue"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"02/01/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires first_coupon > settlement"}, "ODDFPRICE(\"02/01/2017\",\"02/01/2017\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires maturity > first_coupon"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",-1,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires rate >= 0"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,-1,100,2)": {"#NUM!", "ODDFPRICE requires yld >= 0"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,0,2)": {"#NUM!", "ODDFPRICE requires redemption > 0"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"}, "ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"}, // ODDFYIELD "ODDFYIELD()": {"#VALUE!", "ODDFYIELD requires 8 or 9 arguments"}, "ODDFYIELD(\"\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFYIELD(\"02/01/2017\",\"\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"02/01/2017\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires settlement > issue"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"02/01/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires first_coupon > settlement"}, "ODDFYIELD(\"02/01/2017\",\"02/01/2017\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires maturity > first_coupon"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",-1,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires rate >= 0"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,0,100,2)": {"#NUM!", "ODDFYIELD requires pr > 0"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,0,2)": {"#NUM!", "ODDFYIELD requires redemption > 0"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"}, "ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"}, // ODDLPRICE "ODDLPRICE()": {"#VALUE!", "ODDLPRICE requires 7 or 8 arguments"}, "ODDLPRICE(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLPRICE(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires settlement > last_interest"}, "ODDLPRICE(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires maturity > settlement"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLPRICE requires rate >= 0"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,-1,100,2)": {"#NUM!", "ODDLPRICE requires yld >= 0"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLPRICE requires redemption > 0"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"}, "ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"}, // ODDLYIELD "ODDLYIELD()": {"#VALUE!", "ODDLYIELD requires 7 or 8 arguments"}, "ODDLYIELD(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLYIELD(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires settlement > last_interest"}, "ODDLYIELD(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires maturity > settlement"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLYIELD requires rate >= 0"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,0,100,2)": {"#NUM!", "ODDLYIELD requires pr > 0"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLYIELD requires redemption > 0"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"}, "ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"}, // PDURATION "PDURATION()": {"#VALUE!", "PDURATION requires 3 arguments"}, "PDURATION(\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PDURATION(0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PDURATION(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PDURATION(0,0,0)": {"#NUM!", "#NUM!"}, // PMT "PMT()": {"#VALUE!", "PMT requires at least 3 arguments"}, "PMT(0,0,0,0,0,0)": {"#VALUE!", "PMT allows at most 5 arguments"}, "PMT(0,0,0,0,2)": {"#N/A", "#N/A"}, "PMT(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PMT(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PMT(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PMT(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PMT(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // PRICE "PRICE()": {"#VALUE!", "PRICE requires 6 or 7 arguments"}, "PRICE(\"\",\"02/01/2020\",12%,10%,100,2,4)": {"#VALUE!", "#VALUE!"}, "PRICE(\"04/01/2012\",\"\",12%,10%,100,2,4)": {"#VALUE!", "#VALUE!"}, "PRICE(\"04/01/2012\",\"02/01/2020\",\"\",10%,100,2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,\"\",100,2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,\"\",2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICE(\"04/01/2012\",\"02/01/2020\",-1,10%,100,2,4)": {"#NUM!", "PRICE requires rate >= 0"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,-1,100,2,4)": {"#NUM!", "PRICE requires yld >= 0"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,0,2,4)": {"#NUM!", "PRICE requires redemption > 0"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,\"\")": {"#NUM!", "#NUM!"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,3,4)": {"#NUM!", "#NUM!"}, "PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,5)": {"#NUM!", "invalid basis"}, // PPMT "PPMT()": {"#VALUE!", "PPMT requires at least 4 arguments"}, "PPMT(0,0,0,0,0,0,0)": {"#VALUE!", "PPMT allows at most 6 arguments"}, "PPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"}, "PPMT(0,-1,0,0,0,0)": {"#N/A", "#N/A"}, "PPMT(0,1,0,0,0,0)": {"#N/A", "#N/A"}, "PPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // PRICEDISC "PRICEDISC()": {"#VALUE!", "PRICEDISC requires 4 or 5 arguments"}, "PRICEDISC(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"}, "PRICEDISC(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"}, "PRICEDISC(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "PRICEDISC requires maturity > settlement"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "PRICEDISC requires discount > 0"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "PRICEDISC requires redemption > 0"}, "PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"}, // PRICEMAT "PRICEMAT()": {"#VALUE!", "PRICEMAT requires 5 or 6 arguments"}, "PRICEMAT(\"\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"}, "PRICEMAT(\"04/01/2017\",\"\",\"01/01/2017\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,\"\")": {"#NUM!", "#NUM!"}, "PRICEMAT(\"03/31/2021\",\"04/01/2017\",\"01/01/2017\",4.5%,2.5%)": {"#NUM!", "PRICEMAT requires maturity > settlement"}, "PRICEMAT(\"01/01/2017\",\"03/31/2021\",\"04/01/2017\",4.5%,2.5%)": {"#NUM!", "PRICEMAT requires settlement > issue"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",-1,2.5%)": {"#NUM!", "PRICEMAT requires rate >= 0"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,-1)": {"#NUM!", "PRICEMAT requires yld >= 0"}, "PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,5)": {"#NUM!", "invalid basis"}, // PV "PV()": {"#VALUE!", "PV requires at least 3 arguments"}, "PV(10%/4,16,2000,0,1,0)": {"#VALUE!", "PV allows at most 5 arguments"}, "PV(\"\",16,2000,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PV(10%/4,\"\",2000,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PV(10%/4,16,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PV(10%/4,16,2000,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "PV(10%/4,16,2000,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // RATE "RATE()": {"#VALUE!", "RATE requires at least 3 arguments"}, "RATE(48,-200,8000,3,1,0.5,0)": {"#VALUE!", "RATE allows at most 6 arguments"}, "RATE(\"\",-200,8000,3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RATE(48,\"\",8000,3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RATE(48,-200,\"\",3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RATE(48,-200,8000,\"\",1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RATE(48,-200,8000,3,\"\",0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RATE(48,-200,8000,3,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // RECEIVED "RECEIVED()": {"#VALUE!", "RECEIVED requires at least 4 arguments"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,1,0)": {"#VALUE!", "RECEIVED allows at most 5 arguments"}, "RECEIVED(\"\",\"03/31/2016\",1000,4.5%,1)": {"#VALUE!", "#VALUE!"}, "RECEIVED(\"04/01/2011\",\"\",1000,4.5%,1)": {"#VALUE!", "#VALUE!"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",\"\",4.5%,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,\"\")": {"#NUM!", "#NUM!"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,0)": {"#NUM!", "RECEIVED requires discount > 0"}, "RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,5)": {"#NUM!", "invalid basis"}, // RRI "RRI()": {"#VALUE!", "RRI requires 3 arguments"}, "RRI(\"\",\"\",\"\")": {"#NUM!", "#NUM!"}, "RRI(0,10000,15000)": {"#NUM!", "RRI requires nper argument to be > 0"}, "RRI(10,0,15000)": {"#NUM!", "RRI requires pv argument to be > 0"}, "RRI(10,10000,-1)": {"#NUM!", "RRI requires fv argument to be >= 0"}, // SLN "SLN()": {"#VALUE!", "SLN requires 3 arguments"}, "SLN(\"\",\"\",\"\")": {"#NUM!", "#NUM!"}, "SLN(10000,1000,0)": {"#NUM!", "SLN requires life argument to be > 0"}, // SYD "SYD()": {"#VALUE!", "SYD requires 4 arguments"}, "SYD(\"\",\"\",\"\",\"\")": {"#NUM!", "#NUM!"}, "SYD(10000,1000,0,1)": {"#NUM!", "SYD requires life argument to be > 0"}, "SYD(10000,1000,5,0)": {"#NUM!", "SYD requires per argument to be > 0"}, "SYD(10000,1000,1,5)": {"#NUM!", "#NUM!"}, // TBILLEQ "TBILLEQ()": {"#VALUE!", "TBILLEQ requires 3 arguments"}, "TBILLEQ(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLEQ(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLEQ(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"}, "TBILLEQ(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"}, "TBILLEQ(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"}, "TBILLEQ(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"}, // TBILLPRICE "TBILLPRICE()": {"#VALUE!", "TBILLPRICE requires 3 arguments"}, "TBILLPRICE(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLPRICE(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLPRICE(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"}, "TBILLPRICE(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"}, "TBILLPRICE(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"}, "TBILLPRICE(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"}, // TBILLYIELD "TBILLYIELD()": {"#VALUE!", "TBILLYIELD requires 3 arguments"}, "TBILLYIELD(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLYIELD(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"}, "TBILLYIELD(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"}, "TBILLYIELD(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"}, "TBILLYIELD(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"}, "TBILLYIELD(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"}, // VDB "VDB()": {"#VALUE!", "VDB requires 5 or 7 arguments"}, "VDB(-1,1000,5,0,1)": {"#NUM!", "VDB requires cost >= 0"}, "VDB(10000,-1,5,0,1)": {"#NUM!", "VDB requires salvage >= 0"}, "VDB(10000,1000,0,0,1)": {"#NUM!", "VDB requires life > 0"}, "VDB(10000,1000,5,-1,1)": {"#NUM!", "VDB requires start_period > 0"}, "VDB(10000,1000,5,2,1)": {"#NUM!", "VDB requires start_period <= end_period"}, "VDB(10000,1000,5,0,6)": {"#NUM!", "VDB requires end_period <= life"}, "VDB(10000,1000,5,0,1,-0.2)": {"#VALUE!", "VDB requires factor >= 0"}, "VDB(\"\",1000,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VDB(10000,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VDB(10000,1000,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VDB(10000,1000,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VDB(10000,1000,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "VDB(10000,1000,5,0,1,\"\")": {"#NUM!", "#NUM!"}, "VDB(10000,1000,5,0,1,0.2,\"\")": {"#NUM!", "#NUM!"}, // YIELD "YIELD()": {"#VALUE!", "YIELD requires 6 or 7 arguments"}, "YIELD(\"\",\"06/30/2015\",10%,101,100,4)": {"#VALUE!", "#VALUE!"}, "YIELD(\"01/01/2010\",\"\",10%,101,100,4)": {"#VALUE!", "#VALUE!"}, "YIELD(\"01/01/2010\",\"06/30/2015\",\"\",101,100,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,\"\",100,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,\"\")": {"#NUM!", "#NUM!"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,3)": {"#NUM!", "#NUM!"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,5)": {"#NUM!", "invalid basis"}, "YIELD(\"01/01/2010\",\"06/30/2015\",-1,101,100,4)": {"#NUM!", "YIELD requires rate >= 0"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,0,100,4)": {"#NUM!", "YIELD requires pr > 0"}, "YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,-1,4)": {"#NUM!", "YIELD requires redemption >= 0"}, // YIELDDISC "YIELDDISC()": {"#VALUE!", "YIELDDISC requires 4 or 5 arguments"}, "YIELDDISC(\"\",\"06/30/2017\",97,100,0)": {"#VALUE!", "#VALUE!"}, "YIELDDISC(\"01/01/2017\",\"\",97,100,0)": {"#VALUE!", "#VALUE!"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",\"\",100,0)": {"#VALUE!", "#VALUE!"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,\"\",0)": {"#VALUE!", "#VALUE!"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,\"\")": {"#NUM!", "#NUM!"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",0,100)": {"#NUM!", "YIELDDISC requires pr > 0"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,0)": {"#NUM!", "YIELDDISC requires redemption > 0"}, "YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,5)": {"#NUM!", "invalid basis"}, // YIELDMAT "YIELDMAT()": {"#VALUE!", "YIELDMAT requires 5 or 6 arguments"}, "YIELDMAT(\"\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": {"#VALUE!", "#VALUE!"}, "YIELDMAT(\"01/01/2017\",\"\",\"06/01/2014\",5.5%,101,0)": {"#VALUE!", "#VALUE!"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"\",5.5%,101,0)": {"#VALUE!", "#VALUE!"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",\"\",101,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,5.5%,\"\")": {"#NUM!", "#NUM!"}, "YIELDMAT(\"06/01/2014\",\"06/30/2018\",\"01/01/2017\",5.5%,101,0)": {"#NUM!", "YIELDMAT requires settlement > issue"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",-1,101,0)": {"#NUM!", "YIELDMAT requires rate >= 0"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",1,0,0)": {"#NUM!", "YIELDMAT requires pr > 0"}, "YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,5)": {"#NUM!", "invalid basis"}, // DISPIMG "_xlfn.DISPIMG()": {"#VALUE!", "DISPIMG requires 2 numeric arguments"}, } for formula, expected := range mathCalcError { f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } referenceCalc := map[string]string{ // MDETERM "MDETERM(A1:B2)": "-3", // PRODUCT "PRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4", // IMPRODUCT "IMPRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4", // SUM "A1/A3": "0.333333333333333", "SUM(A1:A2)": "3", "SUM(Sheet1!A1:Sheet1!A2)": "3", "SUM(Sheet1!A1,A2)": "3", "(-2-SUM(-4+A2))*5": "0", "SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5", "SUM(A1,A2,A3)*SUM(2,3)": "30", "1+SUM(SUM(A1+A2/A3)*(2-3),2)": "1.33333333333333", "A1/A2/SUM(A1:A2:B1)": "0.0416666666666667", "A1/A2/SUM(A1:A2:B1)*A3": "0.125", "SUM(B1:D1)": "4", "SUM(\"X\")": "0", } for formula, expected := range referenceCalc { f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, expected, result, formula) } referenceCalcError := map[string][]string{ // MDETERM "MDETERM(A1:B3)": {"#VALUE!", "#VALUE!"}, // SUM "1+SUM(SUM(A1+A2/A4)*(2-3),2)": {"#VALUE!", "#DIV/0!"}, } for formula, expected := range referenceCalcError { f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } volatileFuncs := []string{ "NOW()", "RAND()", "RANDBETWEEN(1,2)", "TODAY()", } for _, formula := range volatileFuncs { f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) _, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) } // Test get calculated cell value on not formula cell f := prepareCalcData(cellData) result, err := f.CalcCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "1", result) // Test get calculated cell value on not exists worksheet f = prepareCalcData(cellData) _, err = f.CalcCellValue("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get calculated cell value with invalid sheet name _, err = f.CalcCellValue("Sheet:1", "A1") assert.Equal(t, ErrSheetNameInvalid, err) // Test get calculated cell value with not support formula f = prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "UNSUPPORT(A1)")) _, err = f.CalcCellValue("Sheet1", "A1") assert.EqualError(t, err, "not support UNSUPPORT function") assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx"))) } func TestCalcWithDefinedName(t *testing.T) { cellData := [][]interface{}{ {"A1_as_string", "B1_as_string", 123, nil}, } f := prepareCalcData(cellData) assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!A1", Scope: "Workbook"})) assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!B1", Scope: "Sheet1"})) assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name2", RefersTo: "Sheet1!C1", Scope: "Workbook"})) assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "defined_name1")) result, err := f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) // DefinedName with scope WorkSheet takes precedence over DefinedName with scope Workbook, so we should get B1 value assert.Equal(t, "B1_as_string", result, "defined_name1") assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "CONCATENATE(\"<\",defined_name1,\">\")")) result, err = f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) assert.Equal(t, "", result, "defined_name1") // comparing numeric values assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "123=defined_name2")) result, err = f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) assert.Equal(t, "TRUE", result, "123=defined_name2") // comparing text values assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "\"B1_as_string\"=defined_name1")) result, err = f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) assert.Equal(t, "TRUE", result, "\"B1_as_string\"=defined_name1") // comparing text values assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")")) result, err = f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) assert.Equal(t, "YES", result, "IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")") t.Run("for_sheet_name_with_space", func(t *testing.T) { f := NewFile() defer func() { assert.NoError(t, f.Close()) }() assert.NoError(t, f.SetSheetName("Sheet1", "Sheet 1")) cells := []string{"A1", "A2", "A3", "A4"} names := []string{"val1", "val2", "val3", "val4"} for idx, v := range []interface{}{100, 20, 30, 5} { assert.NoError(t, f.SetCellValue("Sheet 1", cells[idx], v)) } for idx, cell := range cells { assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: names[idx], RefersTo: "'Sheet 1'!" + cell, })) } assert.NoError(t, f.SetCellFormula("Sheet 1", "B1", "=val1-val2-val3-val4")) result, err := f.CalcCellValue("Sheet 1", "B1") assert.NoError(t, err) assert.Equal(t, "45", result) }) } func TestCalcISBLANK(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, }) fn := formulaFuncs{} result := fn.ISBLANK(argsList) assert.Equal(t, "TRUE", result.Value()) assert.Empty(t, result.Error) } func TestCalcAND(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, }) fn := formulaFuncs{} result := fn.AND(argsList) assert.Equal(t, result.String, "") assert.Empty(t, result.Error) } func TestCalcOR(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, }) fn := formulaFuncs{} result := fn.OR(argsList) assert.Equal(t, result.Value(), "FALSE") assert.Empty(t, result.Error) } func TestCalcDet(t *testing.T) { assert.Equal(t, det([][]float64{ {1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, }), float64(0)) } func TestCalcToBool(t *testing.T) { b := newBoolFormulaArg(true).ToBool() assert.Equal(t, b.Boolean, true) assert.Equal(t, b.Number, 1.0) } func TestCalcToList(t *testing.T) { assert.Equal(t, []formulaArg(nil), newEmptyFormulaArg().ToList()) formulaList := []formulaArg{newEmptyFormulaArg()} assert.Equal(t, formulaList, newListFormulaArg(formulaList).ToList()) } func TestCalcCompareFormulaArg(t *testing.T) { assert.Equal(t, compareFormulaArg(newEmptyFormulaArg(), newEmptyFormulaArg(), newNumberFormulaArg(matchModeMaxLess), false), criteriaEq) lhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg()}) rhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg(), newEmptyFormulaArg()}) assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaL) assert.Equal(t, compareFormulaArg(rhs, lhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG) lhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)}) rhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)}) assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaEq) lhs = newListFormulaArg([]formulaArg{newNumberFormulaArg(1)}) rhs = newListFormulaArg([]formulaArg{newNumberFormulaArg(0)}) assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG) assert.Equal(t, compareFormulaArg(formulaArg{Type: ArgUnknown}, formulaArg{Type: ArgUnknown}, newNumberFormulaArg(matchModeMaxLess), false), criteriaErr) } func TestCalcCompareFormulaArgMatrix(t *testing.T) { lhs := newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg()}}) rhs := newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg(), newEmptyFormulaArg()}}) assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaL) lhs = newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg(), newEmptyFormulaArg()}}) rhs = newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg()}}) assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG) lhs = newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}}) rhs = newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(0)}}) assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG) } func TestCalcANCHORARRAY(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 2)) formulaType, ref := STCellFormulaTypeArray, "B1:B2" assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY($B$1))")) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "3", result) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY(\"\",\"\"))")) result, err = f.CalcCellValue("Sheet1", "C1") assert.EqualError(t, err, "ANCHORARRAY requires 1 numeric argument") assert.Equal(t, "#VALUE!", result) fn := &formulaFuncs{f: f, sheet: "SheetN"} argsList := list.New() argsList.PushBack(newStringFormulaArg("$B$1")) formulaArg := fn.ANCHORARRAY(argsList) assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value()) fn.sheet = "Sheet1" argsList = argsList.Init() arg := newStringFormulaArg("$A$1") arg.cellRefs = list.New() arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1}) argsList.PushBack(arg) formulaArg = fn.ANCHORARRAY(argsList) assert.Equal(t, ArgEmpty, formulaArg.Type) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{} formulaArg = fn.ANCHORARRAY(argsList) assert.Equal(t, ArgError, formulaArg.Type) assert.Equal(t, ErrParameterInvalid.Error(), formulaArg.Value()) argsList = argsList.Init() arg = newStringFormulaArg("$B$1") arg.cellRefs = list.New() arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1, Sheet: "SheetN"}) argsList.PushBack(arg) ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{Ref: "A1:A1"} formulaArg = fn.ANCHORARRAY(argsList) assert.Equal(t, ArgError, formulaArg.Type) assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value()) } func TestCalcArrayFormula(t *testing.T) { t.Run("matrix_multiplication", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2})) assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4})) formulaType, ref := STCellFormulaTypeArray, "C1:C2" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2", FormulaOpts{Ref: &ref, Type: &formulaType})) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "2", result) result, err = f.CalcCellValue("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "12", result) }) t.Run("matrix_multiplication_with_defined_name", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2})) assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4})) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "matrix", RefersTo: "Sheet1!$A$1:$A$2", })) formulaType, ref := STCellFormulaTypeArray, "C1:C2" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "matrix*B1:B2+\"1\"", FormulaOpts{Ref: &ref, Type: &formulaType})) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "3", result) result, err = f.CalcCellValue("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "13", result) }) t.Run("columm_multiplication", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2})) assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4})) formulaType, ref := STCellFormulaTypeArray, "C1:C1048576" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A:A*B:B", FormulaOpts{Ref: &ref, Type: &formulaType})) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "2", result) result, err = f.CalcCellValue("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "12", result) }) t.Run("row_multiplication", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2})) assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4})) formulaType, ref := STCellFormulaTypeArray, "A3:XFD3" assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "1:1*2:2", FormulaOpts{Ref: &ref, Type: &formulaType})) result, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, "3", result) result, err = f.CalcCellValue("Sheet1", "B3") assert.NoError(t, err) assert.Equal(t, "8", result) }) } func TestCalcTRANSPOSE(t *testing.T) { cellData := [][]interface{}{ {"a", "d"}, {"b", "e"}, {"c", "f"}, } formula := "=TRANSPOSE(A1:A3)" f := prepareCalcData(cellData) formulaType, ref := STCellFormulaTypeArray, "D1:F2" assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula, FormulaOpts{Ref: &ref, Type: &formulaType})) _, err := f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err, formula) } func TestCalcVLOOKUP(t *testing.T) { cellData := [][]interface{}{ {nil, nil, nil, nil, nil, nil}, {nil, "Score", "Grade", nil, nil, nil}, {nil, 0, "F", nil, "Score", 85}, {nil, 60, "D", nil, "Grade"}, {nil, 70, "C", nil, nil, nil}, {nil, 80, "b", nil, nil, nil}, {nil, 90, "A", nil, nil, nil}, {nil, 85, "B", nil, nil, nil}, {nil, nil, nil, nil, nil, nil}, } f := prepareCalcData(cellData) calc := map[string]string{ "VLOOKUP(F3,B3:C8,2)": "b", "VLOOKUP(F3,B3:C8,2,TRUE)": "b", "VLOOKUP(F3,B3:C8,2,FALSE)": "B", } for formula, expected := range calc { assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula)) result, err := f.CalcCellValue("Sheet1", "F4") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "VLOOKUP(INT(1),C3:C3,1,FALSE)": {"#N/A", "VLOOKUP no result found"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula)) result, err := f.CalcCellValue("Sheet1", "F4") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } argsList := list.New() argsList.PushBack(newStringFormulaArg("")) argsList.PushBack(newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}})) argsList.PushBack(newNumberFormulaArg(1)) argsList.PushBack(newStringFormulaArg("")) _, _, _, _, err := checkHVLookupArgs("VLOOKUP", argsList) assert.Equal(t, ArgError, err.Type) } func TestCalcBoolean(t *testing.T) { cellData := [][]interface{}{{0.5, "TRUE", -0.5, "FALSE", true}} f := prepareCalcData(cellData) formulaList := map[string]string{ "AVERAGEA(A1:C1)": "0.333333333333333", "MAX(0.5,B1)": "0.5", "MAX(A1:B1)": "0.5", "MAXA(A1:B1)": "0.5", "MAXA(A1:E1)": "1", "MAXA(0.5,B1)": "1", "MIN(-0.5,D1)": "-0.5", "MIN(C1:D1)": "-0.5", "MINA(C1:D1)": "-0.5", "MINA(-0.5,D1)": "-0.5", "STDEV(A1:C1)": "0.707106781186548", "STDEV(A1,B1,C1)": "0.707106781186548", "STDEVA(A1:C1,B1)": "0.707106781186548", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula)) result, err := f.CalcCellValue("Sheet1", "B10") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcMAXMIN(t *testing.T) { cellData := [][]interface{}{{"1"}, {"2"}, {true}} f := prepareCalcData(cellData) formulaList := map[string]string{ "MAX(A1:A3)": "0", "MAXA(A1:A3)": "1", "MIN(A1:A3)": "0", "MINA(A1:A3)": "1", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcAVERAGEIF(t *testing.T) { f := prepareCalcData([][]interface{}{ {"Monday", 500}, {"Tuesday", 50}, {"Thursday", 100}, {"Friday", 100}, {"Thursday", 200}, {5, 300}, {2, 200}, {3, 100}, {4, 50}, {5, 100}, {1, 50}, {true, 200}, {true, 250}, {false, 50}, }) for formula, expected := range map[string]string{ "AVERAGEIF(A1:A14,\"Thursday\",B1:B14)": "150", "AVERAGEIF(A1:A14,5,B1:B14)": "200", "AVERAGEIF(A1:A14,\">2\",B1:B14)": "137.5", "AVERAGEIF(A1:A14,TRUE,B1:B14)": "225", "AVERAGEIF(A1:A14,\"<>TRUE\",B1:B14)": "150", } { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcCOVAR(t *testing.T) { cellData := [][]interface{}{ {"array1", "array2"}, {2, 22.9}, {7, 33.49}, {8, 34.5}, {3, 27.61}, {4, 19.5}, {1, 10.11}, {6, 37.9}, {5, 31.08}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "COVAR(A1:A9,B1:B9)": "16.633125", "COVAR(A2:A9,B2:B9)": "16.633125", "COVARIANCE.P(A1:A9,B1:B9)": "16.633125", "COVARIANCE.P(A2:A9,B2:B9)": "16.633125", "COVARIANCE.S(A1:A9,B1:B9)": "19.0092857142857", "COVARIANCE.S(A2:A9,B2:B9)": "19.0092857142857", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "COVAR()": {"#VALUE!", "COVAR requires 2 arguments"}, "COVAR(A2:A9,B3:B3)": {"#N/A", "#N/A"}, "COVARIANCE.P()": {"#VALUE!", "COVARIANCE.P requires 2 arguments"}, "COVARIANCE.P(A2:A9,B3:B3)": {"#N/A", "#N/A"}, "COVARIANCE.S()": {"#VALUE!", "COVARIANCE.S requires 2 arguments"}, "COVARIANCE.S(A2:A9,B3:B3)": {"#N/A", "#N/A"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcUniqueExactlyOnce(t *testing.T) { cellData := [][]interface{}{ {"Customer name"}, {"Fife, Grant"}, {"Pruitt, Barbara"}, {"Horn, Frances"}, {"Barrett, Alicia"}, {"Barrett, Alicia"}, {"Larson, Lynn"}, {"Pruitt, Barbara"}, {"Snook, Anthony"}, {"Snook, Anthony"}, {"Horn, Frances"}, {"Brown, Charity"}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,TRUE))": "Fife, Grant:Larson, Lynn:Brown, Charity", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,FALSE))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcUniqueMultiColumn(t *testing.T) { cellData := [][]interface{}{ {"Player name", "Gender", "Nickname"}, {"Tom", "M", "Tom"}, {"Fred", "M", "Fred"}, {"Amy", "F", "Amy"}, {"John", "M", "John"}, {"Malicia", "F", "Malicia"}, {"Fred", "M", "Fred"}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7))": "Tom:M:Tom:Fred:M:Fred:Amy:F:Amy:John:M:John:Malicia:F:Malicia", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE))": "Tom:M:Fred:M:Amy:F:John:M:Malicia:F:Fred:M", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE, TRUE))": "M:M:F:M:F:M", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcUniqueErrors(t *testing.T) { cellData := [][]interface{}{ {"Player name", "Gender", "Nickname"}, {"Tom", "M", "Tom"}, {"Fred", "M", "Fred"}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "TEXTJOIN(\":\", TRUE, UNIQUE())": "#VALUE!", "TEXTJOIN(\":\", TRUE, UNIQUE(1, 2, 3, 4))": "#VALUE!", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, \"Hello\"))": "#VALUE!", "TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, TRUE, \"Hello\"))": "#VALUE!", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Error(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestTransposeFormulaArgsMatrix(t *testing.T) { assert.Empty(t, transposeFormulaArgsMatrix([][]formulaArg{})) } func TestGetFormulaUniqueArgs(t *testing.T) { argsList := list.New() emptyArg := newEmptyFormulaArg() argsList.PushBack(emptyArg) _, err := getFormulaUniqueArgs(argsList) assert.Equal(t, "missing first argument to UNIQUE", err.Error) argsList = list.New() argsList.PushBack(newListFormulaArg([]formulaArg{newErrorFormulaArg(formulaErrorNAME, formulaErrorNAME)})) _, err = getFormulaUniqueArgs(argsList) assert.Equal(t, formulaErrorNAME, err.Error) } func TestCalcDatabase(t *testing.T) { cellData := [][]interface{}{ {"Tree", "Height", "Age", "Yield", "Profit", "Height"}, {nil, ">1000%", nil, nil, nil, "<16"}, {}, {"Tree", "Height", "Age", "Yield", "Profit"}, {"Apple", 18, 20, 14, 105}, {"Pear", 12, 12, 10, 96}, {"Cherry", 13, 14, 9, 105}, {"Apple", 14, nil, 10, 75}, {"Pear", 9, 8, 8, 77}, {"Apple", 12, 11, 6, 45}, } f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "\"=Apple\"")) assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "\"=Pear\"")) assert.NoError(t, f.SetCellFormula("Sheet1", "C8", "NA()")) formulaList := map[string]string{ "DAVERAGE(A4:E10,\"Profit\",A1:F3)": "73.25", "DCOUNT(A4:E10,\"Age\",A1:F2)": "1", "DCOUNT(A4:E10,,A1:F2)": "2", "DCOUNT(A4:E10,\"Profit\",A1:F2)": "2", "DCOUNT(A4:E10,\"Tree\",A1:F2)": "0", "DCOUNT(A4:E10,\"Age\",A2:F3)": "0", "DCOUNTA(A4:E10,\"Age\",A1:F2)": "1", "DCOUNTA(A4:E10,,A1:F2)": "2", "DCOUNTA(A4:E10,\"Profit\",A1:F2)": "2", "DCOUNTA(A4:E10,\"Tree\",A1:F2)": "2", "DCOUNTA(A4:E10,\"Age\",A2:F3)": "0", "DGET(A4:E6,\"Profit\",A1:F3)": "96", "DMAX(A4:E10,\"Tree\",A1:F3)": "0", "DMAX(A4:E10,\"Profit\",A1:F3)": "96", "DMIN(A4:E10,\"Tree\",A1:F3)": "0", "DMIN(A4:E10,\"Profit\",A1:F3)": "45", "DPRODUCT(A4:E10,\"Profit\",A1:F3)": "24948000", "DSTDEV(A4:E10,\"Profit\",A1:F3)": "21.077238908358", "DSTDEVP(A4:E10,\"Profit\",A1:F3)": "18.2534243362718", "DSUM(A4:E10,\"Profit\",A1:F3)": "293", "DVAR(A4:E10,\"Profit\",A1:F3)": "444.25", "DVARP(A4:E10,\"Profit\",A1:F3)": "333.1875", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula)) result, err := f.CalcCellValue("Sheet1", "A11") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "DAVERAGE()": {"#VALUE!", "DAVERAGE requires 3 arguments"}, "DAVERAGE(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DAVERAGE(A4:E10,\"Tree\",A1:F3)": {"#DIV/0!", "#DIV/0!"}, "DCOUNT()": {"#VALUE!", "DCOUNT requires at least 2 arguments"}, "DCOUNT(A4:E10,\"Age\",A1:F2,\"\")": {"#VALUE!", "DCOUNT allows at most 3 arguments"}, "DCOUNT(A4,\"Age\",A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNT(A4:E10,NA(),A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNT(A4:E4,,A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNT(A4:E10,\"x\",A2:F3)": {"#VALUE!", "#VALUE!"}, "DCOUNTA()": {"#VALUE!", "DCOUNTA requires at least 2 arguments"}, "DCOUNTA(A4:E10,\"Age\",A1:F2,\"\")": {"#VALUE!", "DCOUNTA allows at most 3 arguments"}, "DCOUNTA(A4,\"Age\",A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNTA(A4:E10,NA(),A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNTA(A4:E4,,A1:F2)": {"#VALUE!", "#VALUE!"}, "DCOUNTA(A4:E10,\"x\",A2:F3)": {"#VALUE!", "#VALUE!"}, "DGET()": {"#VALUE!", "DGET requires 3 arguments"}, "DGET(A1,\"Profit\",A1)": {"#VALUE!", "#VALUE!"}, "DGET(A4:E5,\"Profit\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DGET(A4:E10,\"Profit\",A1:F3)": {"#NUM!", "#NUM!"}, "DMAX()": {"#VALUE!", "DMAX requires 3 arguments"}, "DMAX(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DMIN()": {"#VALUE!", "DMIN requires 3 arguments"}, "DMIN(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DPRODUCT()": {"#VALUE!", "DPRODUCT requires 3 arguments"}, "DPRODUCT(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DSTDEV()": {"#VALUE!", "DSTDEV requires 3 arguments"}, "DSTDEV(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DSTDEVP()": {"#VALUE!", "DSTDEVP requires 3 arguments"}, "DSTDEVP(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DSUM()": {"#VALUE!", "DSUM requires 3 arguments"}, "DSUM(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DVAR()": {"#VALUE!", "DVAR requires 3 arguments"}, "DVAR(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, "DVARP()": {"#VALUE!", "DVARP requires 3 arguments"}, "DVARP(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula)) result, err := f.CalcCellValue("Sheet1", "A11") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcDBCS(t *testing.T) { f := NewFile(Options{CultureInfo: CultureNameZhCN}) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "DBCS(\"`~·!@#$¥%…^&*()_-+=[]{}\\|;:'\"\"<,>.?/01234567890 abc ABC \uff65\uff9e\uff9f \uff74\uff78\uff7e\uff99\")")) result, err := f.CalcCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "\uff40\uff5e\u00b7\uff01\uff20\uff03\uff04\u00a5\uff05\u2026\uff3e\uff06\uff0a\uff08\uff09\uff3f\uff0d\uff0b\uff1d\uff3b\uff3d\uff5b\uff5d\uff3c\uff5c\uff1b\uff1a\uff07\uff02\uff1c\uff0c\uff1e\uff0e\uff1f\uff0f\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19\uff10\u3000\uff41\uff42\uff43\u3000\uff21\uff22\uff23\u3000\uff65\uff9e\uff9f\u3000\uff74\uff78\uff7e\uff99", result) } func TestCalcFORMULATEXT(t *testing.T) { f, formulaText := NewFile(), "=SUM(B1:C1)" assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formulaText)) for _, formula := range []string{"FORMULATEXT(A1)", "FORMULATEXT(A:A)", "FORMULATEXT(A1:B1)"} { assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula), formula) result, err := f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err, formula) assert.Equal(t, formulaText, result, formula) } } func TestCalcGROWTHandTREND(t *testing.T) { cellData := [][]interface{}{ {"known_x's", "known_y's", 0, -1}, {1, 10, 1}, {2, 20, 1}, {3, 40}, {4, 80}, {}, {"new_x's", "new_y's"}, {5}, {6}, {7}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "GROWTH(A2:B2)": "1", "GROWTH(B2:B5,A2:A5,A8:A10)": "160", "GROWTH(B2:B5,A2:A5,A8:A10,FALSE)": "467.842838114059", "GROWTH(A4:A5,A2:B3,A8:A10,FALSE)": "", "GROWTH(A3:A5,A2:B4,A2:B3)": "2", "GROWTH(A4:A5,A2:B3)": "", "GROWTH(A2:B2,A2:B3)": "", "GROWTH(A2:B2,A2:B3,A2:B3,FALSE)": "1.28402541668774", "GROWTH(A2:B2,A4:B5,A4:B5,FALSE)": "1", "GROWTH(A3:C3,A2:C3,A2:B3)": "2", "TREND(A2:B2)": "1", "TREND(B2:B5,A2:A5,A8:A10)": "95", "TREND(B2:B5,A2:A5,A8:A10,FALSE)": "81.6666666666667", "TREND(A4:A5,A2:B3,A8:A10,FALSE)": "", "TREND(A4:A5,A2:B3,A2:B3,FALSE)": "1.5", "TREND(A3:A5,A2:B4,A2:B3)": "2", "TREND(A4:A5,A2:B3)": "", "TREND(A2:B2,A2:B3)": "", "TREND(A2:B2,A2:B3,A2:B3,FALSE)": "1", "TREND(A2:B2,A4:B5,A4:B5,FALSE)": "1", "TREND(A3:C3,A2:C3,A2:B3)": "2", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "GROWTH()": {"#VALUE!", "GROWTH requires at least 1 argument"}, "GROWTH(B2:B5,A2:A5,A8:A10,TRUE,0)": {"#VALUE!", "GROWTH allows at most 4 arguments"}, "GROWTH(A1:B1,A2:A5,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"}, "GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"}, "GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": {"#VALUE!", "#VALUE!"}, "GROWTH(B2:B5,A2:A5,A8:A10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "GROWTH(A2:B3,A4:B4)": {"#REF!", "#REF!"}, "GROWTH(A4:B4,A2:A2)": {"#REF!", "#REF!"}, "GROWTH(A2:A2,A4:A5)": {"#REF!", "#REF!"}, "GROWTH(C1:C1,A2:A3)": {"#VALUE!", "#VALUE!"}, "GROWTH(D1:D1,A2:A3)": {"#NUM!", "#NUM!"}, "GROWTH(A2:A3,C1:C1)": {"#VALUE!", "#VALUE!"}, "TREND()": {"#VALUE!", "TREND requires at least 1 argument"}, "TREND(B2:B5,A2:A5,A8:A10,TRUE,0)": {"#VALUE!", "TREND allows at most 4 arguments"}, "TREND(A1:B1,A2:A5,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"}, "TREND(B2:B5,A1:B1,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"}, "TREND(B2:B5,A2:A5,A1:B1,TRUE)": {"#VALUE!", "#VALUE!"}, "TREND(B2:B5,A2:A5,A8:A10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"}, "TREND(A2:B3,A4:B4)": {"#REF!", "#REF!"}, "TREND(A4:B4,A2:A2)": {"#REF!", "#REF!"}, "TREND(A2:A2,A4:A5)": {"#REF!", "#REF!"}, "TREND(C1:C1,A2:A3)": {"#VALUE!", "#VALUE!"}, "TREND(D1:D1,A2:A3)": {"#REF!", "#REF!"}, "TREND(A2:A3,C1:C1)": {"#VALUE!", "#VALUE!"}, "TREND(C1:C1,C1:C1)": {"#VALUE!", "#VALUE!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcHLOOKUP(t *testing.T) { cellData := [][]interface{}{ {"Example Result Table"}, {nil, "A", "B", "C", "E", "F"}, {"Math", .58, .9, .67, .76, .8}, {"French", .61, .71, .59, .59, .76}, {"Physics", .75, .45, .39, .52, .69}, {"Biology", .39, .55, .77, .61, .45}, {}, {"Individual Student Score"}, {"Student:", "Biology Score:"}, {"E"}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "HLOOKUP(A10,A2:F6,5,FALSE)": "0.61", "HLOOKUP(D3,D3:D3,1,TRUE)": "0.67", "HLOOKUP(F3,D3:F3,1,TRUE)": "0.8", "HLOOKUP(A5,A2:F2,1,TRUE)": "F", "HLOOKUP(\"D\",A2:F2,1,TRUE)": "C", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula)) result, err := f.CalcCellValue("Sheet1", "B10") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "HLOOKUP(INT(1),A3:A3,1,FALSE)": {"#N/A", "HLOOKUP no result found"}, "HLOOKUP(4,A1:E1048576,2,TRUE)": {"#N/A", "HLOOKUP no result found"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula)) result, err := f.CalcCellValue("Sheet1", "B10") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcCHITESTandCHISQdotTEST(t *testing.T) { cellData := [][]interface{}{ {nil, "Observed Frequencies", nil, nil, "Expected Frequencies"}, {nil, "men", "women", nil, nil, "men", "women"}, {"answer a", 33, 39, nil, "answer a", 26.25, 31.5}, {"answer b", 62, 62, nil, "answer b", 57.75, 61.95}, {"answer c", 10, 4, nil, "answer c", 21, 11.55}, {nil, -1, 0}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "CHITEST(B3:C5,F3:G5)": "0.000699102758787672", "CHITEST(B3:C3,F3:G3)": "0.0605802098655177", "CHITEST(B3:B4,F3:F4)": "0.152357748933542", "CHITEST(B4:B6,F3:F5)": "7.07076951440726E-25", "CHISQ.TEST(B3:C5,F3:G5)": "0.000699102758787672", "CHISQ.TEST(B3:C3,F3:G3)": "0.0605802098655177", "CHISQ.TEST(B3:B4,F3:F4)": "0.152357748933542", "CHISQ.TEST(B4:B6,F3:F5)": "7.07076951440726E-25", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "I1", formula)) result, err := f.CalcCellValue("Sheet1", "I1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "CHITEST()": {"#VALUE!", "CHITEST requires 2 arguments"}, "CHITEST(MUNIT(0),MUNIT(0))": {"#VALUE!", "#VALUE!"}, "CHITEST(B3:C5,F3:F4)": {"#N/A", "#N/A"}, "CHITEST(B3:B3,F3:F3)": {"#N/A", "#N/A"}, "CHITEST(F3:F5,B4:B6)": {"#NUM!", "#NUM!"}, "CHITEST(F3:F5,C4:C6)": {"#DIV/0!", "#DIV/0!"}, "CHISQ.TEST()": {"#VALUE!", "CHISQ.TEST requires 2 arguments"}, "CHISQ.TEST(B3:C5,F3:F4)": {"#N/A", "#N/A"}, "CHISQ.TEST(B3:B3,F3:F3)": {"#N/A", "#N/A"}, "CHISQ.TEST(F3:F5,B4:B6)": {"#NUM!", "#NUM!"}, "CHISQ.TEST(F3:F5,C4:C6)": {"#DIV/0!", "#DIV/0!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "I1", formula)) result, err := f.CalcCellValue("Sheet1", "I1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcFTEST(t *testing.T) { cellData := [][]interface{}{ {"Group 1", "Group 2"}, {3.5, 9.2}, {4.7, 8.2}, {6.2, 7.3}, {4.9, 6.1}, {3.8, 5.4}, {5.5, 7.8}, {7.1, 5.9}, {6.7, 8.4}, {3.9, 7.7}, {4.6, 6.6}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "FTEST(A2:A11,B2:B11)": "0.95403555939413", "F.TEST(A2:A11,B2:B11)": "0.95403555939413", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "FTEST()": {"#VALUE!", "FTEST requires 2 arguments"}, "FTEST(A2:A2,B2:B2)": {"#DIV/0!", "#DIV/0!"}, "FTEST(A12:A14,B2:B4)": {"#DIV/0!", "#DIV/0!"}, "FTEST(A2:A4,B2:B2)": {"#DIV/0!", "#DIV/0!"}, "FTEST(A2:A4,B12:B14)": {"#DIV/0!", "#DIV/0!"}, "F.TEST()": {"#VALUE!", "F.TEST requires 2 arguments"}, "F.TEST(A2:A2,B2:B2)": {"#DIV/0!", "#DIV/0!"}, "F.TEST(A12:A14,B2:B4)": {"#DIV/0!", "#DIV/0!"}, "F.TEST(A2:A4,B2:B2)": {"#DIV/0!", "#DIV/0!"}, "F.TEST(A2:A4,B12:B14)": {"#DIV/0!", "#DIV/0!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcIRR(t *testing.T) { cellData := [][]interface{}{{-1}, {0.2}, {0.24}, {0.288}, {0.3456}, {0.4147}} f := prepareCalcData(cellData) formulaList := map[string]string{ "IRR(A1:A4)": "-0.136189509034157", "IRR(A1:A6)": "0.130575760006905", "IRR(A1:A4,-0.1)": "-0.136189514994621", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "IRR()": {"#VALUE!", "IRR requires at least 1 argument"}, "IRR(0,0,0)": {"#VALUE!", "IRR allows at most 2 arguments"}, "IRR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "IRR(A2:A3)": {"#NUM!", "#NUM!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcMAXMINIFS(t *testing.T) { f := NewFile() for cell, row := range map[string][]interface{}{ "A1": {1, -math.MaxFloat64 - 1}, "A2": {2, -math.MaxFloat64 - 2}, "A3": {3, math.MaxFloat64 + 1}, "A4": {4, math.MaxFloat64 + 2}, } { assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row)) } formulaList := map[string]string{ "MAX(B1:B2)": "0", "MAXIFS(B1:B2,A1:A2,\">0\")": "0", "MIN(B3:B4)": "0", "MINIFS(B3:B4,A3:A4,\"<0\")": "0", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcMIRR(t *testing.T) { cellData := [][]interface{}{{-100}, {18}, {22.5}, {28}, {35.5}, {45}} f := prepareCalcData(cellData) formulaList := map[string]string{ "MIRR(A1:A6,0.055,0.05)": "0.1000268752662", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "MIRR()": {"#VALUE!", "MIRR requires 3 arguments"}, "MIRR(A1:A5,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MIRR(A1:A5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "MIRR(B1:B5,0,0)": {"#DIV/0!", "#DIV/0!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcSUMIFSAndAVERAGEIFS(t *testing.T) { cellData := [][]interface{}{ {"Quarter", "Area", "Sales Rep.", "Sales"}, {1, "North", "Jeff", 223000}, {1, "North", "Chris", 125000}, {1, "South", "Carol", 456000}, {2, "North", "Jeff", 322000}, {2, "North", "Chris", 340000}, {2, "South", "Carol", 198000}, {3, "North", "Jeff", 310000}, {3, "North", "Chris", 250000}, {3, "South", "Carol", 460000}, {4, "North", "Jeff", 261000}, {4, "North", "Chris", 389000}, {4, "South", "Carol", 305000}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "AVERAGEIFS(D2:D13,A2:A13,1,B2:B13,\"North\")": "174000", "AVERAGEIFS(D2:D13,A2:A13,\">2\",C2:C13,\"Jeff\")": "285500", "SUMIFS(D2:D13,A2:A13,1,B2:B13,\"North\")": "348000", "SUMIFS(D2:D13,A2:A13,\">2\",C2:C13,\"Jeff\")": "571000", "SUMIFS(D2:D13,A2:A13,1,D2:D13,125000)": "125000", "SUMIFS(D2:D13,A2:A13,1,D2:D13,\">100000\",C2:C13,\"Chris\")": "125000", "SUMIFS(D2:D13,A2:A13,1,D2:D13,\"<40000\",C2:C13,\"Chris\")": "0", "SUMIFS(D2:D13,A2:A13,1,A2:A13,2)": "0", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "AVERAGEIFS()": {"#VALUE!", "AVERAGEIFS requires at least 3 arguments"}, "AVERAGEIFS(H1,\"\")": {"#VALUE!", "AVERAGEIFS requires at least 3 arguments"}, "AVERAGEIFS(H1,\"\",TRUE,1)": {"#N/A", "#N/A"}, "AVERAGEIFS(H1,\"\",TRUE)": {"#DIV/0!", "AVERAGEIF divide by zero"}, "AVERAGEIFS(D2:D13,A2:A13,1,A2:A13,2)": {"#DIV/0!", "AVERAGEIF divide by zero"}, "SUMIFS()": {"#VALUE!", "SUMIFS requires at least 3 arguments"}, "SUMIFS(D2:D13,A2:A13,1,B2:B13)": {"#N/A", "#N/A"}, "SUMIFS(D20:D23,A2:A13,\">2\",C2:C13,\"Jeff\")": {"#VALUE!", "#VALUE!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcSUMIFExactMatch(t *testing.T) { cellData := [][]interface{}{ {"Category", "Amount"}, {"text", 100}, {"***_***_text", 200}, {"text", 150}, {"***_text_***", 300}, {"TEXT", 50}, {"other", 400}, } f := prepareCalcData(cellData) formulaList := map[string]string{ `SUMIF(A2:A7,"text",B2:B7)`: "300", `COUNTIF(A2:A7,"text")`: "3", `SUMIF(A2:A7,".",B2:B7)`: "0", `SUMIF(A2:A7,".*",B2:B7)`: "0", `SUMIF(A2:A7,".?",B2:B7)`: "0", `SUMIF(A2:A7,"*text*",B2:B7)`: "800", `SUMIF(A2:A7,"text*",B2:B7)`: "300", `SUMIF(A2:A7,"*text",B2:B7)`: "500", `SUMIF(A2:A7,"*",B2:B7)`: "1200", `SUMIF(A2:A7,"othe?",B2:B7)`: "400", `SUMIF(A2:A7,"~**",B2:B7)`: "500", `COUNTIF(A2:A7,"*text*")`: "5", `COUNTIF(A2:A7,"~*")`: "0", `COUNTIF(A2:A7,"~~")`: "0", `COUNTIF(A2:A7,"~?")`: "0", `COUNTIF(A2:A7,"*~**")`: "2", `COUNTIF(A2:A7,"~*~*~*_~*~*~*_text")`: "1", `COUNTIF(A2:A7,"~*~*~*_text_~*~*~*")`: "1", `COUNTIF(A2:A7,"~a")`: "0", `COUNTIF(A2:A7,"<>text")`: "3", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } cellData = [][]interface{}{ {"Category", "Amount"}, {"text", 10}, {"***text", 20}, {"text ***", 30}, {"TEXT", 40}, {5, 50}, {5.5, 60}, } f = prepareCalcData(cellData) for formula, expected := range map[string]string{ `SUMIF(A2:A7,"text",B2:B7)`: "50", `SUMIF(A2:A7,"*text",B2:B7)`: "70", `SUMIF(A2:A7,"text*",B2:B7)`: "80", `SUMIF(A2:A7,5,B2:B7)`: "50", `COUNTIF(A2:A7,"tex?")`: "2", } { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcXIRR(t *testing.T) { cellData := [][]interface{}{ {-100.00, 42370}, {20.00, 42461}, {40.00, 42644}, {25.00, 42767}, {8.00, 42795}, {15.00, 42887}, {-1e-10, 42979}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "XIRR(A1:A4,B1:B4)": "-0.196743861298328", "XIRR(A1:A6,B1:B6,0.5)": "0.0944390744445204", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "XIRR()": {"#VALUE!", "XIRR requires 2 or 3 arguments"}, "XIRR(A1:A4,B1:B4,-1)": {"#VALUE!", "XIRR requires guess > -1"}, "XIRR(\"\",B1:B4)": {"#VALUE!", "#VALUE!"}, "XIRR(A1:A4,\"\")": {"#VALUE!", "#VALUE!"}, "XIRR(A1:A4,B1:B4,\"\")": {"#NUM!", "#NUM!"}, "XIRR(A2:A6,B2:B6)": {"#NUM!", "#NUM!"}, "XIRR(A2:A7,B2:B7)": {"#NUM!", "#NUM!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcXLOOKUP(t *testing.T) { cellData := [][]interface{}{ {}, {nil, nil, "Quarter", "Gross Profit", "Net profit", "Profit %"}, {nil, nil, "Qtr1", nil, 19342, 29.30}, {}, {nil, "Income Statement", "Qtr1", "Qtr2", "Qtr3", "Qtr4", "Total"}, {nil, "Total sales", 50000, 78200, 89500, 91250, 308.95}, {nil, "Cost of sales", -25000, -42050, -59450, -60450, -186950}, {nil, "Gross Profit", 25000, 36150, 30050, 30800, 122000}, {}, {nil, "Depreciation", -899, -791, -202, -412, -2304}, {nil, "Interest", -513, -853, -150, -956, -2472}, {nil, "Earnings before Tax", 23588, 34506, 29698, 29432, 117224}, {}, {nil, "Tax", -4246, -6211, -5346, -5298, 21100}, {}, {nil, "Net profit", 19342, 28295, 24352, 24134, 96124}, {nil, "Profit %", 0.293, 0.278, 0.234, 0.276, 0.269}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "SUM(XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2))": "87272.293", "SUM(XLOOKUP($C3,$C5:$C5,$C6:$G6,NA(),0,-2))": "309258.95", "SUM(XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,-2))": "87272.293", "SUM(XLOOKUP($C3,$C5:$G5,$C6:$G17,NA(),0,2))": "87272.293", "SUM(XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,2))": "244000", "XLOOKUP(D2,$B6:$B17,C6:C17)": "25000", "XLOOKUP(D2,$B6:$B17,XLOOKUP($C3,$C5:$G5,$C6:$G17))": "25000", "XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),2)": "25000", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula)) result, err := f.CalcCellValue("Sheet1", "D3") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "XLOOKUP()": {"#VALUE!", "XLOOKUP requires at least 3 arguments"}, "XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": {"#VALUE!", "XLOOKUP allows at most 6 arguments"}, "XLOOKUP($C3,$C5,$C6,NA(),0,2)": {"#N/A", "#N/A"}, "XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": {"#VALUE!", "#VALUE!"}, "XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": {"#VALUE!", "#VALUE!"}, "XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": {"#VALUE!", "#VALUE!"}, "XLOOKUP(D2,$B6:$B17,$C6:$G16,NA(),0,2)": {"#VALUE!", "#VALUE!"}, "XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),3,2)": {"#VALUE!", "#VALUE!"}, "XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,0)": {"#VALUE!", "#VALUE!"}, "XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula)) result, err := f.CalcCellValue("Sheet1", "D3") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } cellData = [][]interface{}{ {"Salesperson", "Item", "Amont"}, {"B", "Apples", 30, 25, 15, 50, 45, 18}, {"L", "Oranges", 25, "D3", "E3"}, {"C", "Grapes", 15}, {"L", "Lemons", 50}, {"L", "Oranges", 45}, {"C", "Peaches", 18}, {"B", "Pears", 40}, {"B", "Apples", 55}, } f = prepareCalcData(cellData) formulaList = map[string]string{ // Test match mode with partial match (wildcards) "XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),2)": "30", // Test match mode with approximate match in vertical (next larger item) "XLOOKUP(32,B2:B9,C2:C9,NA(),1)": "30", // Test match mode with approximate match in horizontal (next larger item) "XLOOKUP(30,C2:F2,C3:F3,NA(),1)": "25", // Test match mode with approximate match in vertical (next smaller item) "XLOOKUP(40,C2:C9,B2:B9,NA(),-1)": "Pears", // Test match mode with approximate match in horizontal (next smaller item) "XLOOKUP(29,C2:F2,C3:F3,NA(),-1)": "D3", // Test search mode "XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,1)": "25", "XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,-1)": "45", "XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,2)": "50", "XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,-2)": "45", // Test match mode and search mode "XLOOKUP(29,C2:H2,C3:H3,NA(),-1,-1)": "D3", "XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "D4", formula)) result, err := f.CalcCellValue("Sheet1", "D4") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError = map[string][]string{ // Test match mode with exact match "XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),0)": {"#N/A", "#N/A"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula)) result, err := f.CalcCellValue("Sheet1", "D3") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcXNPV(t *testing.T) { cellData := [][]interface{}{ {nil, 0.05}, {42370, -10000, nil}, {42401, 2000}, {42491, 2400}, {42552, 2900}, {42675, 3500}, {42736, 4100}, {}, {42401}, {42370}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "XNPV(B1,B2:B7,A2:A7)": "4447.93800944052", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "XNPV()": {"#VALUE!", "XNPV requires 3 arguments"}, "XNPV(\"\",B2:B7,A2:A7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "XNPV(0,B2:B7,A2:A7)": {"#VALUE!", "XNPV requires rate > 0"}, "XNPV(B1,\"\",A2:A7)": {"#VALUE!", "#VALUE!"}, "XNPV(B1,B2:B7,\"\")": {"#VALUE!", "#VALUE!"}, "XNPV(B1,B2:B7,C2:C7)": {"#VALUE!", "#VALUE!"}, "XNPV(B1,B2,A2)": {"#NUM!", "#NUM!"}, "XNPV(B1,B2:B3,A2:A5)": {"#NUM!", "#NUM!"}, "XNPV(B1,B2:B3,A9:A10)": {"#VALUE!", "#VALUE!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcMATCH(t *testing.T) { f := NewFile() for cell, row := range map[string][]interface{}{ "A1": {"cccc", 7, 4, 16}, "A2": {"dddd", 2, 6, 11}, "A3": {"aaaa", 4, 7, 10}, "A4": {"bbbb", 1, 10, 7}, "A5": {"eeee", 8, 11, 6}, "A6": {nil, 11, 16, 4}, } { assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row)) } formulaList := map[string]string{ "MATCH(\"aaaa\",A1:A6,0)": "3", "MATCH(\"*b\",A1:A5,0)": "4", "MATCH(\"?eee\",A1:A5,0)": "5", "MATCH(\"?*?e\",A1:A5,0)": "5", "MATCH(\"aaaa\",A1:A6,1)": "3", "MATCH(10,B1:B6)": "5", "MATCH(8,C1:C6,1)": "3", "MATCH(6,B1:B6,-1)": "1", "MATCH(10,D1:D6,-1)": "3", "MATCH(-10,D1:D6,-1)": "6", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string]string{ "MATCH(3,C1:C6,1)": "#N/A", "MATCH(5,C1:C6,-1)": "#N/A", } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.EqualError(t, err, expected, formula) assert.Equal(t, expected, result, formula) } assert.Equal(t, newErrorFormulaArg(formulaErrorNA, formulaErrorNA), calcMatch(2, nil, []formulaArg{})) } func TestCalcISFORMULA(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "ISFORMULA(A1)")) for _, formula := range []string{"NA()", "SUM(A1:A3)"} { assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula)) result, err := f.CalcCellValue("Sheet1", "B1") assert.NoError(t, err, formula) assert.Equal(t, "TRUE", result, formula) } } func TestCalcMODE(t *testing.T) { cellData := [][]interface{}{ {1, 1}, {1, 1}, {2, 2}, {2, 2}, {3, 2}, {3}, {3}, {4}, {4}, {4}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "MODE(A1:A10)": "3", "MODE(B1:B6)": "2", "MODE.MULT(A1:A10)": "3", "MODE.SNGL(A1:A10)": "3", "MODE.SNGL(B1:B6)": "2", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "MODE()": {"#VALUE!", "MODE requires at least 1 argument"}, "MODE(0,\"\")": {"#VALUE!", "#VALUE!"}, "MODE(D1:D3)": {"#N/A", "#N/A"}, "MODE.MULT()": {"#VALUE!", "MODE.MULT requires at least 1 argument"}, "MODE.MULT(0,\"\")": {"#VALUE!", "#VALUE!"}, "MODE.MULT(D1:D3)": {"#N/A", "#N/A"}, "MODE.SNGL()": {"#VALUE!", "MODE.SNGL requires at least 1 argument"}, "MODE.SNGL(0,\"\")": {"#VALUE!", "#VALUE!"}, "MODE.SNGL(D1:D3)": {"#N/A", "#N/A"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcPEARSON(t *testing.T) { cellData := [][]interface{}{ {"x", "y"}, {1, 10.11}, {2, 22.9}, {2, 27.61}, {3, 27.61}, {4, 11.15}, {5, 31.08}, {6, 37.9}, {7, 33.49}, {8, 21.05}, {9, 27.01}, {10, 45.78}, {11, 31.32}, {12, 50.57}, {13, 45.48}, {14, 40.94}, {15, 53.76}, {16, 36.18}, {17, 49.77}, {18, 55.66}, {19, 63.83}, {20, 63.6}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "PEARSON(A2:A22,B2:B22)": "0.864129542184994", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcPROB(t *testing.T) { cellData := [][]interface{}{ {"x", "probability"}, {0, 0.1}, {1, 0.15}, {2, 0.17}, {3, 0.22}, {4, 0.21}, {5, 0.09}, {6, 0.05}, {7, 0.01}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "PROB(A2:A9,B2:B9,3)": "0.22", "PROB(A2:A9,B2:B9,3,5)": "0.52", "PROB(A2:A9,B2:B9,8,10)": "0", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "NA()")) calcError := map[string][]string{ "PROB(A2:A9,B2:B9,3)": {"#NUM!", "#NUM!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcRSQ(t *testing.T) { cellData := [][]interface{}{ {"known_y's", "known_x's"}, {2, 22.9}, {7, 33.49}, {8, 34.5}, {3, 27.61}, {4, 19.5}, {1, 10.11}, {6, 37.9}, {5, 31.08}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "RSQ(A2:A9,B2:B9)": "0.711666290486784", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcSLOP(t *testing.T) { cellData := [][]interface{}{ {"known_x's", "known_y's"}, {1, 3}, {2, 7}, {3, 17}, {4, 20}, {5, 20}, {6, 27}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "SLOPE(A2:A7,B2:B7)": "0.200826446280992", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcSHEET(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) formulaList := map[string]string{ "SHEET(\"Sheet2\")": "2", "SHEET(Sheet2!A1)": "2", "SHEET(Sheet2!A1:A2)": "2", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula)) result, err := f.CalcCellValue("Sheet1", "A1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcSHEETS(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) formulaList := map[string]string{ "SHEETS(Sheet1!A1:B1)": "1", "SHEETS(Sheet1!A1:Sheet1!B1)": "1", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula)) result, err := f.CalcCellValue("Sheet1", "A1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestCalcSTEY(t *testing.T) { cellData := [][]interface{}{ {"known_x's", "known_y's"}, {1, 3}, {2, 7.9}, {3, 8}, {4, 9.2}, {4.5, 12}, {5, 10.5}, {6, 15}, {7, 15.5}, {8, 17}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "STEYX(B2:B11,A2:A11)": "1.20118634668221", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "STEYX()": {"#VALUE!", "STEYX requires 2 arguments"}, "STEYX(B2:B11,A1:A9)": {"#N/A", "#N/A"}, "STEYX(B2,A2)": {"#DIV/0!", "#DIV/0!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcTTEST(t *testing.T) { cellData := [][]interface{}{ {4, 8, nil, 1, 1}, {5, 3, nil, 1, 1}, {2, 7}, {5, 3}, {8, 5}, {9, 2}, {3, 2}, {2, 7}, {3, 9}, {8, 4}, {9, 4}, {5, 7}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "TTEST(A1:A12,B1:B12,1,1)": "0.44907068944428", "TTEST(A1:A12,B1:B12,1,2)": "0.436717306029283", "TTEST(A1:A12,B1:B12,1,3)": "0.436722015384755", "TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559", "TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567", "TTEST(A1:A12,B1:B12,2,3)": "0.873444030769511", "T.TEST(A1:A12,B1:B12,1,1)": "0.44907068944428", "T.TEST(A1:A12,B1:B12,1,2)": "0.436717306029283", "T.TEST(A1:A12,B1:B12,1,3)": "0.436722015384755", "T.TEST(A1:A12,B1:B12,2,1)": "0.898141378888559", "T.TEST(A1:A12,B1:B12,2,2)": "0.873434612058567", "T.TEST(A1:A12,B1:B12,2,3)": "0.873444030769511", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "TTEST()": {"#VALUE!", "TTEST requires 4 arguments"}, "TTEST(\"\",B1:B12,1,1)": {"#NUM!", "#NUM!"}, "TTEST(A1:A12,\"\",1,1)": {"#NUM!", "#NUM!"}, "TTEST(A1:A12,B1:B12,\"\",1)": {"#VALUE!", "#VALUE!"}, "TTEST(A1:A12,B1:B12,1,\"\")": {"#VALUE!", "#VALUE!"}, "TTEST(A1:A12,B1:B12,0,1)": {"#NUM!", "#NUM!"}, "TTEST(A1:A12,B1:B12,1,0)": {"#NUM!", "#NUM!"}, "TTEST(A1:A2,B1:B1,1,1)": {"#N/A", "#N/A"}, "TTEST(A13:A14,B13:B14,1,1)": {"#NUM!", "#NUM!"}, "TTEST(A12:A13,B12:B13,1,1)": {"#DIV/0!", "#DIV/0!"}, "TTEST(A13:A14,B13:B14,1,2)": {"#NUM!", "#NUM!"}, "TTEST(D1:D4,E1:E4,1,3)": {"#NUM!", "#NUM!"}, "T.TEST()": {"#VALUE!", "T.TEST requires 4 arguments"}, "T.TEST(\"\",B1:B12,1,1)": {"#NUM!", "#NUM!"}, "T.TEST(A1:A12,\"\",1,1)": {"#NUM!", "#NUM!"}, "T.TEST(A1:A12,B1:B12,\"\",1)": {"#VALUE!", "#VALUE!"}, "T.TEST(A1:A12,B1:B12,1,\"\")": {"#VALUE!", "#VALUE!"}, "T.TEST(A1:A12,B1:B12,0,1)": {"#NUM!", "#NUM!"}, "T.TEST(A1:A12,B1:B12,1,0)": {"#NUM!", "#NUM!"}, "T.TEST(A1:A2,B1:B1,1,1)": {"#N/A", "#N/A"}, "T.TEST(A13:A14,B13:B14,1,1)": {"#NUM!", "#NUM!"}, "T.TEST(A12:A13,B12:B13,1,1)": {"#DIV/0!", "#DIV/0!"}, "T.TEST(A13:A14,B13:B14,1,2)": {"#NUM!", "#NUM!"}, "T.TEST(D1:D4,E1:E4,1,3)": {"#NUM!", "#NUM!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) { cellData := [][]interface{}{ {"05/01/2019", 43586, "text1"}, {"09/13/2019", 43721, "text2"}, {"10/01/2019", 43739}, {"12/25/2019", 43824}, {"01/01/2020", 43831}, {"01/01/2020", 43831}, {"01/24/2020", 43854}, {"04/04/2020", 43925}, {"05/01/2020", 43952}, {"06/25/2020", 44007}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "NETWORKDAYS(\"01/01/2020\",\"09/12/2020\")": "183", "NETWORKDAYS(\"01/01/2020\",\"09/12/2020\",2)": "183", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\")": "183", "NETWORKDAYS.INTL(\"09/12/2020\",\"01/01/2020\")": "-183", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1)": "183", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",2)": "184", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",3)": "184", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",4)": "183", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",5)": "182", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",6)": "182", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",7)": "182", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",11)": "220", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",12)": "220", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",13)": "220", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",14)": "219", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",15)": "219", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",16)": "219", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",17)": "219", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,A1:A12)": "178", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,B1:B12)": "178", "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,C1:C2)": "183", "WORKDAY(\"12/01/2015\",25)": "42374", "WORKDAY(\"01/01/2020\",123,B1:B12)": "44006", "WORKDAY.INTL(\"12/01/2015\",0)": "42339", "WORKDAY.INTL(\"12/01/2015\",25)": "42374", "WORKDAY.INTL(\"12/01/2015\",-25)": "42304", "WORKDAY.INTL(\"12/01/2015\",25,1)": "42374", "WORKDAY.INTL(\"12/01/2015\",25,2)": "42374", "WORKDAY.INTL(\"12/01/2015\",25,3)": "42372", "WORKDAY.INTL(\"12/01/2015\",25,4)": "42373", "WORKDAY.INTL(\"12/01/2015\",25,5)": "42374", "WORKDAY.INTL(\"12/01/2015\",25,6)": "42374", "WORKDAY.INTL(\"12/01/2015\",25,7)": "42374", "WORKDAY.INTL(\"12/01/2015\",25,11)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,12)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,13)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,14)": "42369", "WORKDAY.INTL(\"12/01/2015\",25,15)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,16)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,17)": "42368", "WORKDAY.INTL(\"12/01/2015\",25,\"0001100\")": "42374", "WORKDAY.INTL(\"01/01/2020\",-123,4)": "43659", "WORKDAY.INTL(\"01/01/2020\",123,4,44010)": "44002", "WORKDAY.INTL(\"01/01/2020\",-123,4,43640)": "43659", "WORKDAY.INTL(\"01/01/2020\",-123,4,43660)": "43658", "WORKDAY.INTL(\"01/01/2020\",-123,7,43660)": "43657", "WORKDAY.INTL(\"01/01/2020\",123,4,A1:A12)": "44008", "WORKDAY.INTL(\"01/01/2020\",123,4,B1:B12)": "44008", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "NETWORKDAYS()": {"#VALUE!", "NETWORKDAYS requires at least 2 arguments"}, "NETWORKDAYS(\"01/01/2020\",\"09/12/2020\",2,\"\")": {"#VALUE!", "NETWORKDAYS requires at most 3 arguments"}, "NETWORKDAYS(\"\",\"09/12/2020\",2)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS(\"01/01/2020\",\"\",2)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL()": {"#VALUE!", "NETWORKDAYS.INTL requires at least 2 arguments"}, "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",4,A1:A12,\"\")": {"#VALUE!", "NETWORKDAYS.INTL requires at most 4 arguments"}, "NETWORKDAYS.INTL(\"01/01/2020\",\"January 25, 100\",4)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(\"\",123,4,B1:B12)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(\"01/01/2020\",123,\"000000x\")": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(\"01/01/2020\",123,\"0000002\")": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",8)": {"#VALUE!", "#VALUE!"}, "NETWORKDAYS.INTL(-1,123)": {"#NUM!", "#NUM!"}, "WORKDAY()": {"#VALUE!", "WORKDAY requires at least 2 arguments"}, "WORKDAY(\"01/01/2020\",123,A1:A12,\"\")": {"#VALUE!", "WORKDAY requires at most 3 arguments"}, "WORKDAY(\"01/01/2020\",\"\",B1:B12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "WORKDAY(\"\",123,B1:B12)": {"#VALUE!", "#VALUE!"}, "WORKDAY(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"}, "WORKDAY(-1,123)": {"#NUM!", "#NUM!"}, "WORKDAY.INTL()": {"#VALUE!", "WORKDAY.INTL requires at least 2 arguments"}, "WORKDAY.INTL(\"01/01/2020\",123,4,A1:A12,\"\")": {"#VALUE!", "WORKDAY.INTL requires at most 4 arguments"}, "WORKDAY.INTL(\"01/01/2020\",\"\",4,B1:B12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "WORKDAY.INTL(\"\",123,4,B1:B12)": {"#VALUE!", "#VALUE!"}, "WORKDAY.INTL(\"01/01/2020\",123,\"\",B1:B12)": {"#VALUE!", "#VALUE!"}, "WORKDAY.INTL(\"01/01/2020\",123,\"000000x\")": {"#VALUE!", "#VALUE!"}, "WORKDAY.INTL(\"01/01/2020\",123,\"0000002\")": {"#VALUE!", "#VALUE!"}, "WORKDAY.INTL(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"}, "WORKDAY.INTL(-1,123)": {"#NUM!", "#NUM!"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.Equal(t, expected[0], result, formula) assert.EqualError(t, err, expected[1], formula) } } func TestCalcZTEST(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{4, 5, 2, 5, 8, 9, 3, 2, 3, 8, 9, 5})) formulaList := map[string]string{ "Z.TEST(A1:L1,5)": "0.371103278558538", "Z.TEST(A1:L1,6)": "0.838129187019751", "Z.TEST(A1:L1,5,1)": "0.193238115385616", "ZTEST(A1:L1,5)": "0.371103278558538", "ZTEST(A1:L1,6)": "0.838129187019751", "ZTEST(A1:L1,5,1)": "0.193238115385616", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "M1", formula)) result, err := f.CalcCellValue("Sheet1", "M1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestStrToDate(t *testing.T) { _, _, _, _, err := strToDate("") assert.Equal(t, formulaErrorVALUE, err.Error) } func TestGetYearDays(t *testing.T) { for _, data := range [][]int{{2021, 0, 360}, {2000, 1, 366}, {2021, 1, 365}, {2000, 3, 365}} { assert.Equal(t, data[2], getYearDays(data[0], data[1])) } } func TestCalcGetBetaHelperContFrac(t *testing.T) { assert.Equal(t, 1.0, getBetaHelperContFrac(1, 0, 1)) } func TestCalcGetBetaDistPDF(t *testing.T) { assert.Equal(t, 0.0, getBetaDistPDF(0.5, 2000, 3)) assert.Equal(t, 0.0, getBetaDistPDF(0, 1, 0)) } func TestCalcD1mach(t *testing.T) { assert.Equal(t, 0.0, d1mach(6)) } func TestCalcChebyshevInit(t *testing.T) { assert.Equal(t, 0, chebyshevInit(0, 0, nil)) assert.Equal(t, 0, chebyshevInit(1, 0, []float64{0})) } func TestCalcChebyshevEval(t *testing.T) { assert.True(t, math.IsNaN(chebyshevEval(0, 0, nil))) } func TestCalcLgammacor(t *testing.T) { assert.True(t, math.IsNaN(lgammacor(9))) assert.Equal(t, 4.930380657631324e-32, lgammacor(3.7451940309632633e+306)) assert.Equal(t, 8.333333333333334e-10, lgammacor(10e+07)) } func TestCalcLgammaerr(t *testing.T) { assert.True(t, math.IsNaN(logrelerr(-2))) } func TestCalcLogBeta(t *testing.T) { assert.True(t, math.IsNaN(logBeta(-1, -1))) assert.Equal(t, math.MaxFloat64, logBeta(0, 0)) } func TestCalcBetainvProbIterator(t *testing.T) { assert.Equal(t, 1.0, betainvProbIterator(1, 1, 1, 1, 1, 1, 1, 1, 1)) } func TestCalcRangeResolver(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "SUM(Sheet1!B:B)")) cellRefs := list.New() cellRanges := list.New() // Test extract value from ranges on invalid ranges cellRanges.PushBack(cellRange{ From: cellRef{Col: 1, Row: 1, Sheet: "SheetN"}, To: cellRef{Col: 1, Row: TotalRows, Sheet: "SheetN"}, }) _, err := f.rangeResolver(&calcContext{}, cellRefs, cellRanges) assert.EqualError(t, err, "sheet SheetN does not exist") ws, err := f.workSheetReader("Sheet1") ws.SheetData.Row = make([]xlsxRow, TotalRows+1) ws.SheetData.Row[TotalRows].C = make([]xlsxC, 3) assert.NoError(t, err) cellRanges.Init() cellRanges.PushBack(cellRange{ From: cellRef{Col: 3, Row: TotalRows, Sheet: "Sheet1"}, To: cellRef{Col: 3, Row: TotalRows + 1, Sheet: "Sheet1"}, }) _, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges) assert.Equal(t, ErrMaxRows, err) // Test extract value from references with invalid references cellRanges.Init() cellRefs.PushBack(cellRef{Col: 1, Row: 1, Sheet: "SheetN"}) _, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges) assert.EqualError(t, err, "sheet SheetN does not exist") cellRefs.Init() cellRefs.PushBack(cellRef{Col: 1, Row: TotalRows + 1, Sheet: "SheetN"}) _, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges) assert.Equal(t, ErrMaxRows, err) t.Run("for_range_resolver_error", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", "test")) cellRefs := list.New() cellRanges := list.New() cellRanges.PushBack(cellRange{ From: cellRef{Col: 1, Row: 1, Sheet: "Sheet1"}, To: cellRef{Col: 1, Row: 1, Sheet: "Sheet1"}, }) f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err := f.rangeResolver(&calcContext{}, cellRefs, cellRanges) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") }) } func TestCalcBahttextAppendDigit(t *testing.T) { assert.Empty(t, bahttextAppendDigit("", -1)) } func TestNestedFunctionsWithOperators(t *testing.T) { f := NewFile() formulaList := map[string]string{ "LEN(\"KEEP\")": "4", "LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\")": "4", "RIGHT(\"REMOVEKEEP\", 4)": "KEEP", "RIGHT(\"REMOVEKEEP\", 10 - 6))": "KEEP", "RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - 6)": "KEEP", "RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOV\") - 1)": "KEEP", "RIGHT(\"REMOVEKEEP\", 10 - LEN(\"REMOVE\"))": "KEEP", "RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\"))": "KEEP", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } } func TestFormulaRawCellValueOption(t *testing.T) { f := NewFile() rawTest := []struct { value string raw bool expected string }{ {"=VALUE(\"1.0E-07\")", false, "0.00"}, {"=VALUE(\"1.0E-07\")", true, "0.0000001"}, {"=\"text\"", false, "$text"}, {"=\"text\"", true, "text"}, {"=\"10e3\"", false, "$10e3"}, {"=\"10e3\"", true, "10e3"}, {"=\"10\" & \"e3\"", false, "$10e3"}, {"=\"10\" & \"e3\"", true, "10e3"}, {"=10e3", false, "10000.00"}, {"=10e3", true, "10000"}, {"=\"1111111111111111\"", false, "$1111111111111111"}, {"=\"1111111111111111\"", true, "1111111111111111"}, {"=1111111111111111", false, "1111111111111110.00"}, {"=1111111111111111", true, "1.11111111111111E+15"}, {"=1444.00000000003", false, "1444.00"}, {"=1444.00000000003", true, "1444.00000000003"}, {"=1444.000000000003", false, "1444.00"}, {"=1444.000000000003", true, "1444"}, {"=ROUND(1444.00000000000003,2)", false, "1444.00"}, {"=ROUND(1444.00000000000003,2)", true, "1444"}, } exp := "0.00;0.00;;$@" styleID, err := f.NewStyle(&Style{CustomNumFmt: &exp}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID)) for _, test := range rawTest { assert.NoError(t, f.SetCellFormula("Sheet1", "A1", test.value)) val, err := f.CalcCellValue("Sheet1", "A1", Options{RawCellValue: test.raw}) assert.NoError(t, err) assert.Equal(t, test.expected, val) } } func TestFormulaArgToToken(t *testing.T) { assert.Equal(t, efp.Token{ TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeLogical, TValue: "TRUE", }, formulaArgToToken(newBoolFormulaArg(true)), ) } func TestPrepareTrendGrowth(t *testing.T) { assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxX([][]float64{{0, 0}, {0, 0}})) assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxY(false, [][]float64{{0, 0}, {0, 0}})) info, err := prepareTrendGrowth(false, [][]float64{{0, 0}, {0, 0}}, [][]float64{{0, 0}, {0, 0}}) assert.Nil(t, info) assert.Equal(t, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM), err) } func TestCalcColRowQRDecomposition(t *testing.T) { assert.False(t, calcRowQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0)) assert.False(t, calcColQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0)) } func TestCalcCellResolver(t *testing.T) { f := NewFile() // Test reference a cell multiple times in a formula assert.NoError(t, f.SetCellValue("Sheet1", "A1", "VALUE1")) assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "A1")) for formula, expected := range map[string]string{ "CONCATENATE(A1,\"_\",A1)": "VALUE1_VALUE1", "CONCATENATE(A1,\"_\",A2)": "VALUE1_VALUE1", "CONCATENATE(A2,\"_\",A2)": "VALUE1_VALUE1", } { assert.NoError(t, f.SetCellFormula("Sheet1", "A3", formula)) result, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } // Test calculates formula that contains a nested argument function which returns a numeric result f = NewFile() for _, cell := range []string{"A1", "B2", "B3", "B4"} { assert.NoError(t, f.SetCellValue("Sheet1", cell, "ABC")) } for cell, formula := range map[string]string{ "A2": "IF(B2<>\"\",MAX(A1:A1)+1,\"\")", "A3": "IF(B3<>\"\",MAX(A1:A2)+1,\"\")", "A4": "IF(B4<>\"\",MAX(A1:A3)+1,\"\")", } { assert.NoError(t, f.SetCellFormula("Sheet1", cell, formula)) } for cell, expected := range map[string]string{"A2": "1", "A3": "2", "A4": "3"} { result, err := f.CalcCellValue("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, result) } // Test calculates formula that reference date and error type cells assert.NoError(t, f.SetCellValue("Sheet1", "C1", "20200208T080910.123")) assert.NoError(t, f.SetCellValue("Sheet1", "C2", "2020-07-10 15:00:00.000")) assert.NoError(t, f.SetCellValue("Sheet1", "C3", formulaErrorDIV)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[2].T = "d" ws.(*xlsxWorksheet).SheetData.Row[0].C[2].V = "20200208T080910.123" ws.(*xlsxWorksheet).SheetData.Row[1].C[2].T = "d" ws.(*xlsxWorksheet).SheetData.Row[1].C[2].V = "2020-07-10 15:00:00.000" ws.(*xlsxWorksheet).SheetData.Row[2].C[2].T = "e" ws.(*xlsxWorksheet).SheetData.Row[2].C[2].V = formulaErrorDIV for _, tbl := range [][]string{ {"D1", "SUM(C1,1)", "43870.3397004977"}, {"D2", "LEN(C2)", "23"}, {"D3", "IFERROR(C3,TRUE)", "TRUE"}, } { assert.NoError(t, f.SetCellFormula("Sheet1", tbl[0], tbl[1])) result, err := f.CalcCellValue("Sheet1", tbl[0]) assert.NoError(t, err) assert.Equal(t, tbl[2], result) } // Test calculates formula that reference invalid cell assert.NoError(t, f.SetCellValue("Sheet1", "E1", "E1")) assert.NoError(t, f.SetCellFormula("Sheet1", "F1", "LEN(E1)")) f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err := f.CalcCellValue("Sheet1", "F1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestEvalInfixExp(t *testing.T) { f := NewFile() arg, err := f.evalInfixExp(nil, "Sheet1", "A1", []efp.Token{ {TSubType: efp.TokenSubTypeRange, TValue: "1A"}, }) assert.Equal(t, arg, newEmptyFormulaArg()) assert.Equal(t, formulaErrorNAME, err.Error()) } func TestParseToken(t *testing.T) { f := NewFile() assert.Equal(t, formulaErrorNAME, f.parseToken(nil, "Sheet1", efp.Token{TSubType: efp.TokenSubTypeRange, TValue: "1A"}, nil, nil, ).Error()) } func TestCalcCellValueCache(t *testing.T) { t.Run("for_calc_call_value_with_cache", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 40)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 50)) assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2")) result1, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, "90", result1) result2, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, result1, result2, "cached result should be consistent") assert.NoError(t, f.SetCellValue("Sheet1", "A1", 60)) result3, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, "110", result3) assert.NotEqual(t, result1, result3, "result should be updated after cache clear") }) t.Run("for_calc_call_value_with_multiple_dependent_cells", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 10)) assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2")) assert.NoError(t, f.SetCellFormula("Sheet1", "A4", "A3*3")) assert.NoError(t, f.SetCellFormula("Sheet1", "A5", "A3+A4")) result3, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, "20", result3) result4, err := f.CalcCellValue("Sheet1", "A4") assert.NoError(t, err) assert.Equal(t, "60", result4) result5, err := f.CalcCellValue("Sheet1", "A5") assert.NoError(t, err) assert.Equal(t, "80", result5) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 20)) newResult3, err := f.CalcCellValue("Sheet1", "A3") assert.NoError(t, err) assert.Equal(t, "30", newResult3) assert.NotEqual(t, result3, newResult3, "A3 should be updated") newResult5, err := f.CalcCellValue("Sheet1", "A5") assert.NoError(t, err) assert.Equal(t, "120", newResult5) assert.NotEqual(t, result5, newResult5, "A5 should be updated") }) t.Run("for_clear_calculation_cache", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10)) assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "A1*2")) result1, err := f.CalcCellValue("Sheet1", "A2") assert.NoError(t, err) assert.Equal(t, "20", result1) result2, err := f.CalcCellValue("Sheet1", "A2") assert.NoError(t, err) assert.Equal(t, result1, result2, "results should be consistent from cache") cases := []struct { name string fn func() error }{ {"SetCellValue", func() error { return f.SetCellValue("Sheet1", "B1", 100) }}, {"SetCellInt", func() error { return f.SetCellInt("Sheet1", "B2", 200) }}, {"SetCellUint", func() error { return f.SetCellUint("Sheet1", "B3", 300) }}, {"SetCellFloat", func() error { return f.SetCellFloat("Sheet1", "B4", 3.14, 2, 64) }}, {"SetCellStr", func() error { return f.SetCellStr("Sheet1", "B5", "test") }}, {"SetCellBool", func() error { return f.SetCellBool("Sheet1", "B6", true) }}, {"SetCellDefault", func() error { return f.SetCellDefault("Sheet1", "B7", "default") }}, {"SetCellFormula", func() error { return f.SetCellFormula("Sheet1", "B8", "=1+1") }}, {"SetCellHyperLink", func() error { return f.SetCellHyperLink("Sheet1", "B9", "https://github.com/xuri/excelize", "External") }}, {"SetCellRichText", func() error { runs := []RichTextRun{{Text: "Rich", Font: &Font{Bold: true}}} return f.SetCellRichText("Sheet1", "B10", runs) }}, {"SetSheetRow", func() error { return f.SetSheetRow("Sheet1", "C1", &[]interface{}{1, 2, 3}) }}, {"SetSheetCol", func() error { return f.SetSheetCol("Sheet1", "D1", &[]interface{}{4, 5, 6}) }}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := f.CalcCellValue("Sheet1", "A2") assert.NoError(t, err) assert.NoError(t, tc.fn()) result, err := f.CalcCellValue("Sheet1", "A2") assert.NoError(t, err) assert.Equal(t, "20", result, "calculation should still work after cache clear") }) } }) } func TestCalcLookupCol(t *testing.T) { result := lookupCol(formulaArg{ Type: ArgMatrix, Matrix: [][]formulaArg{ {newNumberFormulaArg(1), newNumberFormulaArg(2)}, {}, {newNumberFormulaArg(3), newNumberFormulaArg(4)}, }, }, 0) assert.Equal(t, 3, len(result)) assert.Equal(t, "1", result[0].Value()) assert.Equal(t, "", result[1].Value()) assert.Equal(t, "3", result[2].Value()) } func TestCalcLookupLinearSearch(t *testing.T) { lookupValue := newStringFormulaArg("test") lookupArray := formulaArg{ Type: ArgString, Matrix: [][]formulaArg{ {newStringFormulaArg("test")}, }, } matchMode := newNumberFormulaArg(0) searchMode := newNumberFormulaArg(1) idx, wasExact := lookupLinearSearch(false, lookupValue, lookupArray, matchMode, searchMode) assert.Equal(t, 0, idx) assert.True(t, wasExact) } func TestCalcMatchMatrix(t *testing.T) { assert.Equal(t, formulaArg{Type: ArgNumber, Number: 2}, calcMatchMatrix(true, 1, &formulaCriteria{ Type: criteriaEq, Condition: newStringFormulaArg("B"), }, [][]formulaArg{ {newStringFormulaArg("A")}, {newStringFormulaArg("B")}, {newStringFormulaArg("C")}, }), ) } func TestCalcSORTBY(t *testing.T) { cellData := [][]interface{}{ {"Name", "Region", "Sales", "Quarter"}, {"Alice", "North", 1500, 1}, {"Bob", "South", 2000, 2}, {"Alice", "North", 1200, 2}, {"Charlie", "East", 1800, 1}, {"Bob", "South", 2200, 1}, {"David", "West", 1000, 2}, {"Alice", "North", 1700, 3}, {"Test", "Mixed", "Text", 4}, {nil, "Empty", 500, 4}, } f := prepareCalcData(cellData) formulaList := map[string]string{ "TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, C2:C8))": "David,Alice,Alice,Alice,Charlie,Bob,Bob", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, C2:C8, -1))": "Bob,Bob,Charlie,Alice,Alice,Alice,David", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, B2:B8, 1, C2:C8, -1))": "Charlie,Alice,Alice,Alice,Bob,Bob,David", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, D2:D8, 1, B2:B8, 1, C2:C8, -1))": "Charlie,Alice,Bob,Alice,Bob,David,Alice", "TEXTJOIN(\";\", TRUE, SORTBY(A2:C4, C2:C4))": "Alice;North;1200;Alice;North;1500;Bob;South;2000", "TEXTJOIN(\",\", TRUE, SORTBY(C2:C8, A2:B8))": "1500,1200,1700,2000,2200,1800,1000", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A10, C2:C10))": "David,Alice,Alice,Alice,Charlie,Bob,Bob,Test", "TEXTJOIN(\",\", TRUE, SORTBY(D2:D8, D2:D8))": "1,1,1,2,2,2,3", "TEXTJOIN(\",\", TRUE, SORTBY(B2:B8, B2:B8))": "East,North,North,North,South,South,West", } for formula, expected := range formulaList { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) result, err := f.CalcCellValue("Sheet1", "E1") assert.NoError(t, err, formula) assert.Equal(t, expected, result, formula) } calcError := map[string][]string{ "SORTBY(A2:A7)": {"#VALUE!", "SORTBY requires at least 2 arguments"}, "SORTBY(A2:A7, C2:C7, 1, D2:D7, 1, B2:B7, 1, E2:E7, 1)": {"#VALUE!", "SORTBY takes at most 7 arguments, received 9"}, "SORTBY(A2:A7, C2:C7, 1, D2:D7)": {"#VALUE!", "SORTBY requires 2, 3, 5, or 7 arguments, received 4"}, "SORTBY(A2:A7, C2:C7, 1, D2:D7, 1, B2:B7)": {"#VALUE!", "SORTBY requires 2, 3, 5, or 7 arguments, received 6"}, "SORTBY(A2:A7, C2:C5)": {"#VALUE!", "by_array dimensions (4 rows) do not match array dimensions (6 rows)"}, "SORTBY(A2:A7, C2:C7, 2)": {"#VALUE!", "sort_order must be 1 or -1, received 2"}, "SORTBY(A2:A7, C2:C7, 0)": {"#VALUE!", "sort_order must be 1 or -1, received 0"}, } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "F1", formula)) result, err := f.CalcCellValue("Sheet1", "F1") assert.EqualError(t, err, expected[1], formula) assert.Equal(t, expected[0], result, formula) } f = prepareCalcData([][]interface{}{ {"Name", "Score"}, {"Alice", 100}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A2, B2:B2))")) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "Alice", result, "Single row should work") f = prepareCalcData([][]interface{}{ {"Name", "Score"}, {"Alice", 100}, {"Bob", 100}, {"Charlie", 100}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A4, B2:B4))")) result, err = f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, "Alice,Bob,Charlie", result, "Equal values maintain original order") f = prepareCalcData([][]interface{}{ {"Name", "Group", "Score"}, {"Charlie", "A", 90}, {"Alice", "A", 95}, {"Bob", "A", 85}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A4, B2:B4, 1, C2:C4, 1))")) result, err = f.CalcCellValue("Sheet1", "D1") assert.NoError(t, err) assert.Equal(t, "Bob,Charlie,Alice", result, "Second key determines order when first is equal") f = prepareCalcData([][]interface{}{ {"Name", "Score"}, {"Alice", 100}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=SORTBY(A2:A2,{})")) result, err = f.CalcCellValue("Sheet1", "C1") assert.Error(t, err) assert.Equal(t, formulaErrorVALUE, result) f = prepareCalcData([][]interface{}{ {"Name", "Score"}, {"Alice", 100}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=SORTBY({},B2:B2)")) result, err = f.CalcCellValue("Sheet1", "C1") assert.Error(t, err) assert.Equal(t, formulaErrorVALUE, result) f = prepareCalcData([][]interface{}{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }) assert.NoError(t, f.SetCellFormula("Sheet1", "D2", "=SORTBY(NA(),B1:B2)")) result, err = f.CalcCellValue("Sheet1", "D2") assert.Equal(t, formulaErrorNA, result) assert.Equal(t, formulaErrorNA, err.Error()) assert.NoError(t, f.SetCellFormula("Sheet1", "D4", "=SORTBY(A1:A2,NA())")) _, err = f.CalcCellValue("Sheet1", "D4") assert.Equal(t, formulaErrorNA, result) assert.Equal(t, formulaErrorNA, err.Error()) argsList := list.New() argsList.PushBack(newStringFormulaArg("text")) nextByArray, errArg := parseSortOrderArg(argsList.Front(), &sortbyKey{}, 0, 3) assert.Equal(t, "strconv.ParseFloat: parsing \"text\": invalid syntax", errArg.Error) assert.NotNil(t, nextByArray) nextByArray, errArg = parseSortOrderArg(argsList.Front(), &sortbyKey{}, 0, 1) assert.Nil(t, errArg) assert.NotNil(t, nextByArray) } func TestCalcTrendGrowthMultipleRegressionPart2(t *testing.T) { calcTrendGrowthMultipleRegressionPart2(true, false, [][]float64{{1}, {2}, {3}}, [][]float64{}, [][]float64{}, [][]float64{{0}}, 2.0, 0, 0, 3) calcTrendGrowthMultipleRegressionPart2(true, false, [][]float64{{1}, {2}, {3}}, [][]float64{{0}, {0}, {0}}, [][]float64{}, [][]float64{{0}}, 2.0, 0, 1, 3) } func TestCalcTrendGrowthRegression(t *testing.T) { mtx := [][]float64{} calcTrendGrowthRegression(false, false, 0, 0, 0, 0, 0, mtx, mtx, mtx, mtx) } func TestCalcImplicitIntersect(t *testing.T) { f := NewFile() for cell, value := range map[string]interface{}{ "A1": -5, "A2": 10, "A3": -3, "A4": "text", "A5": 7, "D1": 1, "D2": 0, "D3": 1, } { assert.NoError(t, f.SetCellValue("Sheet1", cell, value)) } for cell, expected := range map[string]string{ "B1": "5", "B2": "10", "B3": "3", "B5": "7", } { assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ABS(A1:A5)")) result, err := f.CalcCellValue("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, result) } for cell, expected := range map[string]string{ "C1": "TRUE", "C2": "TRUE", "C3": "TRUE", "C4": "FALSE", "C5": "TRUE", } { assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ISNUMBER(A1:A5)")) result, err := f.CalcCellValue("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, result) } for cell, expected := range map[string]string{ "E1": "yes", "E2": "no", "E3": "yes", } { assert.NoError(t, f.SetCellFormula("Sheet1", cell, "IF(D1:D3,\"yes\",\"no\")")) result, err := f.CalcCellValue("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, result) } assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "ABS(A1:A5)")) _, err := f.CalcCellValue("Sheet1", "B10") assert.NoError(t, err) result, err := formulaCriteriaEval(newStringFormulaArg("text"), &formulaCriteria{ Type: criteriaRegexp, Condition: newStringFormulaArg("text"), }) assert.NoError(t, err) assert.True(t, result) fn := formulaFuncs{} assert.Equal(t, ArgMatrix, fn.implicitIntersect( newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}})).Type, ) } ================================================ FILE: calcchain.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" ) // calcChainReader provides a function to get the pointer to the structure // after deserialization of xl/calcChain.xml. func (f *File) calcChainReader() (*xlsxCalcChain, error) { if f.CalcChain == nil { f.CalcChain = new(xlsxCalcChain) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))). Decode(f.CalcChain); err != nil && err != io.EOF { return f.CalcChain, err } } return f.CalcChain, nil } // calcChainWriter provides a function to save xl/calcChain.xml after // serialize structure. func (f *File) calcChainWriter() { if f.CalcChain != nil && f.CalcChain.C != nil { output, _ := xml.Marshal(f.CalcChain) f.saveFileList(defaultXMLPathCalcChain, output) } } // deleteCalcChain provides a function to remove cell reference on the // calculation chain. func (f *File) deleteCalcChain(index int, cell string) error { calc, err := f.calcChainReader() if err != nil { return err } if calc != nil { calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool { return (c.I != index || c.R != cell) && (c.I != index || cell != "") && (c.I != 0 || c.R != cell) }) } if len(calc.C) == 0 { f.CalcChain = nil f.Pkg.Delete(defaultXMLPathCalcChain) content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for k, v := range content.Overrides { if v.PartName == "/xl/calcChain.xml" { content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) } } } return err } type xlsxCalcChainCollection []xlsxCalcChainC // Filter provides a function to filter calculation chain. func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC { var results []xlsxCalcChainC for _, v := range c { if fn(v) { results = append(results, v) } } return results } // volatileDepsReader provides a function to get the pointer to the structure // after deserialization of xl/volatileDependencies.xml. func (f *File) volatileDepsReader() (*xlsxVolTypes, error) { if f.VolatileDeps == nil { volatileDeps, ok := f.Pkg.Load(defaultXMLPathVolatileDeps) if !ok { return f.VolatileDeps, nil } f.VolatileDeps = new(xlsxVolTypes) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(volatileDeps.([]byte)))). Decode(f.VolatileDeps); err != nil && err != io.EOF { return f.VolatileDeps, err } } return f.VolatileDeps, nil } // volatileDepsWriter provides a function to save xl/volatileDependencies.xml // after serialize structure. func (f *File) volatileDepsWriter() { if f.VolatileDeps != nil { output, _ := xml.Marshal(f.VolatileDeps) f.saveFileList(defaultXMLPathVolatileDeps, output) } } // deleteVolTopicRef provides a function to remove cell reference on the // volatile dependencies topic. func (vt *xlsxVolTypes) deleteVolTopicRef(i1, i2, i3, i4 int) { for i := range vt.VolType[i1].Main[i2].Tp[i3].Tr { if i == i4 { vt.VolType[i1].Main[i2].Tp[i3].Tr = append(vt.VolType[i1].Main[i2].Tp[i3].Tr[:i], vt.VolType[i1].Main[i2].Tp[i3].Tr[i+1:]...) } } } ================================================ FILE: calcchain_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" ) func TestCalcChainReader(t *testing.T) { f := NewFile() // Test read calculation chain with unsupported charset f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) _, err := f.calcChainReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestDeleteCalcChain(t *testing.T) { f := NewFile() f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}} f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{ PartName: "/xl/calcChain.xml", }) assert.NoError(t, f.deleteCalcChain(1, "A1")) f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellFormula("Sheet1", "A1", ""), "XML syntax error on line 1: invalid UTF-8") formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) // Test delete calculation chain with unsupported charset calculation chain f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") // Test delete calculation chain with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") } ================================================ FILE: cell.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "fmt" "math" "os" "reflect" "sort" "strconv" "strings" "time" "github.com/xuri/efp" ) // CellType is the type of cell value type. type CellType byte // Cell value types enumeration. const ( CellTypeUnset CellType = iota CellTypeBool CellTypeDate CellTypeError CellTypeFormula CellTypeInlineString CellTypeNumber CellTypeSharedString ) const ( // STCellFormulaTypeArray defined the formula is an array formula. STCellFormulaTypeArray = "array" // STCellFormulaTypeDataTable defined the formula is a data table formula. STCellFormulaTypeDataTable = "dataTable" // STCellFormulaTypeNormal defined the formula is a regular cell formula. STCellFormulaTypeNormal = "normal" // STCellFormulaTypeShared defined the formula is part of a shared formula. STCellFormulaTypeShared = "shared" ) // cellTypes mapping the cell's data type and enumeration. var cellTypes = map[string]CellType{ "b": CellTypeBool, "d": CellTypeDate, "n": CellTypeNumber, "e": CellTypeError, "s": CellTypeSharedString, "str": CellTypeFormula, "inlineStr": CellTypeInlineString, } // GetCellValue provides a function to get formatted value from cell by given // worksheet name and cell reference in spreadsheet. The return value is // converted to the 'string' data type. This function is concurrency safe. If // the cell format can be applied to the value of a cell, the applied value // will be returned, otherwise the original value will be returned. All cells' // values will be the same in a merged range. func (f *File) GetCellValue(sheet, cell string, opts ...Options) (string, error) { return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { sst, err := f.sharedStringsReader() if err != nil { return "", true, err } val, err := c.getValueFrom(f, sst, f.getOptions(opts...).RawCellValue) return val, true, err }) } // GetCellType provides a function to get the cell's data type by given // worksheet name and cell reference in spreadsheet file. func (f *File) GetCellType(sheet, cell string) (CellType, error) { var ( err error cellTypeStr string cellType CellType ) if cellTypeStr, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { return c.T, true, nil }); err != nil { return CellTypeUnset, err } cellType = cellTypes[cellTypeStr] return cellType, err } // SetCellValue provides a function to set the value of a cell. This function // is concurrency safe. The specified coordinates should not be in the first // row of the table, a complex number can be set with string text. The // following shows the supported data types: // // int // int8 // int16 // int32 // int64 // uint // uint8 // uint16 // uint32 // uint64 // float32 // float64 // string // []byte // time.Duration // time.Time // bool // nil // // Note that default date format is m/d/yy h:mm of time.Time type value. You // can set numbers format by the SetCellStyle function. If you need to set the // specialized date in Excel like January 0, 1900 or February 29, 1900, these // times can not representation in Go language time.Time data type. Please set // the cell value as number 0 or 60, then create and bind the date-time number // format style for the cell. func (f *File) SetCellValue(sheet, cell string, value interface{}) error { var err error switch v := value.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: err = f.setCellIntFunc(sheet, cell, v) case float32: err = f.SetCellFloat(sheet, cell, float64(v), -1, 32) case float64: err = f.SetCellFloat(sheet, cell, v, -1, 64) case string: err = f.SetCellStr(sheet, cell, v) case []byte: err = f.SetCellStr(sheet, cell, string(v)) case time.Duration: _, d := setCellDuration(v) err = f.SetCellDefault(sheet, cell, d) if err != nil { return err } err = f.setDefaultTimeStyle(sheet, cell, getDurationNumFmt(v)) case time.Time: err = f.setCellTimeFunc(sheet, cell, v) case bool: err = f.SetCellBool(sheet, cell, v) case nil: err = f.SetCellDefault(sheet, cell, "") default: err = f.SetCellStr(sheet, cell, fmt.Sprint(value)) } return err } // String extracts characters from a string item. func (x xlsxSI) String() string { var value strings.Builder if x.T != nil { value.WriteString(x.T.Val) } for _, s := range x.R { if s.T != nil { value.WriteString(s.T.Val) } } if value.Len() == 0 { return "" } return bstrUnmarshal(value.String()) } // hasValue determine if cell non-blank value. func (c *xlsxC) hasValue() bool { return c.S != 0 || c.V != "" || c.F != nil || c.T != "" } // removeFormula delete formula for the cell. func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) error { f.clearCalcCache() if c.F != nil && c.Vm == nil { sheetID := f.getSheetID(sheet) if err := f.deleteCalcChain(sheetID, c.R); err != nil { return err } if c.F.T == STCellFormulaTypeShared && c.F.Ref != "" { si := c.F.Si for r, row := range ws.SheetData.Row { for col, cell := range row.C { if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si { ws.SheetData.Row[r].C[col].F = nil ws.formulaSI.Delete(si) _ = f.deleteCalcChain(sheetID, cell.R) } } } } c.F = nil } return nil } // setCellIntFunc is a wrapper of SetCellInt. func (f *File) setCellIntFunc(sheet, cell string, value interface{}) error { var err error switch v := value.(type) { case int: err = f.SetCellInt(sheet, cell, int64(v)) case int8: err = f.SetCellInt(sheet, cell, int64(v)) case int16: err = f.SetCellInt(sheet, cell, int64(v)) case int32: err = f.SetCellInt(sheet, cell, int64(v)) case int64: err = f.SetCellInt(sheet, cell, v) case uint: err = f.SetCellUint(sheet, cell, uint64(v)) case uint8: err = f.SetCellUint(sheet, cell, uint64(v)) case uint16: err = f.SetCellUint(sheet, cell, uint64(v)) case uint32: err = f.SetCellUint(sheet, cell, uint64(v)) case uint64: err = f.SetCellUint(sheet, cell, v) } return err } // setCellTimeFunc provides a method to process time type of value for // SetCellValue. func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } c, col, row, err := ws.prepareCell(cell) if err != nil { return err } ws.mu.Lock() c.S = ws.prepareCellStyle(col, row, c.S) ws.mu.Unlock() var date1904, isNum bool wb, err := f.workbookReader() if err != nil { return err } if wb != nil && wb.WorkbookPr != nil { date1904 = wb.WorkbookPr.Date1904 } if isNum, err = c.setCellTime(value, date1904); err != nil { return err } if isNum { _ = f.setDefaultTimeStyle(sheet, cell, getTimeNumFmt(value)) } return err } // setCellTime prepares cell type and Excel time by given Go time.Time type // timestamp. func (c *xlsxC) setCellTime(value time.Time, date1904 bool) (isNum bool, err error) { var excelTime float64 _, offset := value.In(value.Location()).Zone() value = value.Add(time.Duration(offset) * time.Second) if excelTime, err = timeToExcelTime(value, date1904); err != nil { return } isNum = excelTime > 0 if isNum { c.setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64)) } else { c.setCellDefault(value.Format(time.RFC3339Nano)) } return } // setCellDuration prepares cell type and value by given Go time.Duration type // time duration. func setCellDuration(value time.Duration) (t string, v string) { v = strconv.FormatFloat(value.Seconds()/86400, 'f', -1, 32) return } // SetCellInt provides a function to set int type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellInt(sheet, cell string, value int64) error { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) c.T, c.V = setCellInt(value) c.IS = nil return f.removeFormula(c, ws, sheet) } // setCellInt prepares cell type and string type cell value by a given integer. func setCellInt(value int64) (t string, v string) { v = strconv.FormatInt(value, 10) return } // SetCellUint provides a function to set uint type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellUint(sheet, cell string, value uint64) error { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) c.T, c.V = setCellUint(value) c.IS = nil return f.removeFormula(c, ws, sheet) } // setCellUint prepares cell type and string type cell value by a given unsigned // integer. func setCellUint(value uint64) (t string, v string) { v = strconv.FormatUint(value, 10) return } // SetCellBool provides a function to set bool type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellBool(sheet, cell string, value bool) error { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) c.T, c.V = setCellBool(value) c.IS = nil return f.removeFormula(c, ws, sheet) } // setCellBool prepares cell type and string type cell value by a given boolean // value. func setCellBool(value bool) (t string, v string) { t = "b" if value { v = "1" } else { v = "0" } return } // SetCellFloat sets a floating point value into a cell. The precision // parameter specifies how many places after the decimal will be shown // while -1 is a special value that will use as many decimal places as // necessary to represent the number. bitSize is 32 or 64 depending on if a // float32 or float64 was originally used for the value. For Example: // // var x float32 = 1.325 // f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error { if math.IsNaN(value) || math.IsInf(value, 0) { return f.SetCellStr(sheet, cell, fmt.Sprint(value)) } f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) c.setCellFloat(value, precision, bitSize) return f.removeFormula(c, ws, sheet) } // setCellFloat prepares cell type and string type cell value by a given float // value. func (c *xlsxC) setCellFloat(value float64, precision, bitSize int) { if math.IsNaN(value) || math.IsInf(value, 0) { c.setInlineStr(fmt.Sprint(value)) return } c.T, c.V = "", strconv.FormatFloat(value, 'f', precision, bitSize) c.IS = nil } // SetCellStr provides a function to set string type value of a cell. Total // number of characters that a cell can contain 32767 characters. func (f *File) SetCellStr(sheet, cell, value string) error { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) if c.T, c.V, err = f.setCellString(value); err != nil { return err } c.IS = nil return f.removeFormula(c, ws, sheet) } // setCellString provides a function to set string type to shared string table. func (f *File) setCellString(value string) (t, v string, err error) { if countUTF16String(value) > TotalCellChars { value = truncateUTF16Units(value, TotalCellChars) } t = "s" var si int if si, err = f.setSharedString(value); err != nil { return } v = strconv.Itoa(si) return } // sharedStringsLoader load shared string table from system temporary file to // memory, and reset shared string table for reader. func (f *File) sharedStringsLoader() (err error) { f.mu.Lock() defer f.mu.Unlock() if path, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { f.Pkg.Store(defaultXMLPathSharedStrings, f.readBytes(defaultXMLPathSharedStrings)) f.tempFiles.Delete(defaultXMLPathSharedStrings) if err = os.Remove(path.(string)); err != nil { return } f.SharedStrings = nil } if f.sharedStringTemp != nil { if err := f.sharedStringTemp.Close(); err != nil { return err } f.tempFiles.Delete(defaultTempFileSST) f.sharedStringItem, err = nil, os.Remove(f.sharedStringTemp.Name()) f.sharedStringTemp = nil } return } // setSharedString provides a function to add string to the share string table. func (f *File) setSharedString(val string) (int, error) { if err := f.sharedStringsLoader(); err != nil { return 0, err } sst, err := f.sharedStringsReader() if err != nil { return 0, err } f.mu.Lock() defer f.mu.Unlock() if i, ok := f.sharedStringsMap[val]; ok { return i, nil } sst.mu.Lock() defer sst.mu.Unlock() t := xlsxT{Val: val} val, t.Space = trimCellValue(val, false) sst.SI = append(sst.SI, xlsxSI{T: &t}) sst.Count = len(sst.SI) sst.UniqueCount = sst.Count f.sharedStringsMap[val] = sst.UniqueCount - 1 return sst.UniqueCount - 1, nil } // trimCellValue provides a function to set string type to cell. func trimCellValue(value string, escape bool) (v string, ns xml.Attr) { if countUTF16String(value) > TotalCellChars { value = truncateUTF16Units(value, TotalCellChars) } if value != "" { prefix, suffix := value[0], value[len(value)-1] for _, ascii := range []byte{9, 10, 13, 32} { if prefix == ascii || suffix == ascii { ns = xml.Attr{ Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve", } break } } if escape { var buf strings.Builder _ = xml.EscapeText(&buf, []byte(value)) value = strings.ReplaceAll(buf.String(), " ", "\n") } } v = bstrMarshal(value) return } // setCellValue set cell data type and value for (inline) rich string cell or // formula cell. func (c *xlsxC) setCellValue(val string) { if c.F != nil { c.setStr(val) return } c.setInlineStr(val) } // setInlineStr set cell data type and value which containing an (inline) rich // string. func (c *xlsxC) setInlineStr(val string) { c.T, c.V, c.IS = "inlineStr", "", &xlsxSI{T: &xlsxT{}} c.IS.T.Val, c.IS.T.Space = trimCellValue(val, true) } // setStr set cell data type and value which containing a formula string. func (c *xlsxC) setStr(val string) { c.T, c.IS = "str", nil c.V, c.XMLSpace = trimCellValue(val, false) } // getCellBool parse cell value which containing a boolean. func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) { if !raw { if c.V == "1" { return "TRUE", nil } if c.V == "0" { return "FALSE", nil } } return f.formattedValue(c, raw, CellTypeBool) } // setCellDefault prepares cell type and string type cell value by a given // string. func (c *xlsxC) setCellDefault(value string) { if ok, _, _ := isNumeric(value); !ok { if value != "" { c.setInlineStr(value) c.IS.T.Val = value return } c.T, c.V, c.IS = value, value, nil return } c.T, c.V = "", value } // getCellDate parse cell value which contains a date in the ISO 8601 format. func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) { if !raw { layout := "20060102T150405.999" if strings.HasSuffix(c.V, "Z") { layout = "20060102T150405Z" if strings.Contains(c.V, "-") { layout = "2006-01-02T15:04:05Z" } } else if strings.Contains(c.V, "-") { layout = "2006-01-02 15:04:05Z" } if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil { excelTime, _ := timeToExcelTime(timestamp, false) c.V = strconv.FormatFloat(excelTime, 'G', 15, 64) } } return f.formattedValue(c, raw, CellTypeDate) } // getValueFrom return a value from a column/row cell, this function is // intended to be used with for range on rows an argument with the spreadsheet // opened file. func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { switch c.T { case "b": return c.getCellBool(f, raw) case "d": return c.getCellDate(f, raw) case "s": if c.V != "" { xlsxSI, _ := strconv.Atoi(strings.TrimSpace(c.V)) if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { return f.formattedValue(&xlsxC{S: c.S, V: f.getFromStringItem(xlsxSI)}, raw, CellTypeSharedString) } d.mu.Lock() defer d.mu.Unlock() if len(d.SI) > xlsxSI { return f.formattedValue(&xlsxC{S: c.S, V: d.SI[xlsxSI].String()}, raw, CellTypeSharedString) } } return f.formattedValue(c, raw, CellTypeSharedString) case "str": return c.V, nil case "inlineStr": if c.IS != nil { return f.formattedValue(&xlsxC{S: c.S, V: c.IS.String()}, raw, CellTypeInlineString) } return f.formattedValue(c, raw, CellTypeInlineString) default: if isNum, precision, decimal := isNumeric(c.V); isNum && !raw { if precision > 15 { c.V = strconv.FormatFloat(decimal, 'G', 15, 64) } else { c.V = strconv.FormatFloat(decimal, 'f', -1, 64) } } return f.formattedValue(c, raw, CellTypeNumber) } } // SetCellDefault provides a function to set string type value of a cell as // default format without escaping the cell. func (f *File) SetCellDefault(sheet, cell, value string) error { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() c, col, row, err := ws.prepareCell(cell) if err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) c.setCellDefault(value) return f.removeFormula(c, ws, sheet) } // GetCellFormula provides a function to get formula from cell by given // worksheet name and cell reference in spreadsheet. func (f *File) GetCellFormula(sheet, cell string) (string, error) { return f.getCellFormula(sheet, cell, false) } // getCellFormula provides a function to get transformed formula from cell by // given worksheet name and cell reference in spreadsheet. func (f *File) getCellFormula(sheet, cell string, transformed bool) (string, error) { return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { if transformed && !f.formulaChecked { if err := f.setArrayFormulaCells(); err != nil { return "", false, err } f.formulaChecked = true } if transformed && c.f != "" { return c.f, true, nil } if c.F == nil { return "", false, nil } if c.F.T == STCellFormulaTypeShared && c.F.Si != nil { formula, err := getSharedFormula(x, *c.F.Si, c.R) return formula, true, err } return c.F.Content, true, nil }) } // FormulaOpts can be passed to SetCellFormula to use other formula types. type FormulaOpts struct { Type *string // Formula type Ref *string // Shared formula ref } // SetCellFormula provides a function to set formula on the cell is taken // according to the given worksheet name and cell formula settings. The result // of the formula cell can be calculated when the worksheet is opened by the // Office Excel application or can be using the "CalcCellValue" function also // can get the calculated cell value. If the Excel application doesn't // calculate the formula automatically when the workbook has been opened, // please call "UpdateLinkedValue" after setting the cell formula functions. // // Example 1, set normal formula "SUM(A1,B1)" for the cell "A3" on "Sheet1": // // err := f.SetCellFormula("Sheet1", "A3", "SUM(A1,B1)") // // Example 2, set one-dimensional vertical constant array (column array) formula // "1,2,3" for the cell "A3" on "Sheet1": // // err := f.SetCellFormula("Sheet1", "A3", "{1;2;3}") // // Example 3, set one-dimensional horizontal constant array (row array) // formula '"a","b","c"' for the cell "A3" on "Sheet1": // // err := f.SetCellFormula("Sheet1", "A3", "{\"a\",\"b\",\"c\"}") // // Example 4, set two-dimensional constant array formula '{1,2,"a","b"}' for // the cell "A3" on "Sheet1": // // formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" // err := f.SetCellFormula("Sheet1", "A3", "{1,2;\"a\",\"b\"}", // excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) // // Example 5, set range array formula "A1:A2" for the cell "A3" on "Sheet1": // // formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" // err := f.SetCellFormula("Sheet1", "A3", "A1:A2", // excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) // // Example 6, set shared formula "A1+B1" for the cell "C1:C5" // on "Sheet1", "C1" is the master cell: // // formulaType, ref := excelize.STCellFormulaTypeShared, "C1:C5" // err := f.SetCellFormula("Sheet1", "C1", "A1+B1", // excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) // // Example 7, set table formula "SUM(Table1[[A]:[B]])" for the cell "C2" // on "Sheet1": // // package main // // import ( // "fmt" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { // if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil { // fmt.Println(err) // return // } // } // if err := f.AddTable("Sheet1", &excelize.Table{ // Range: "A1:C2", Name: "Table1", StyleName: "TableStyleMedium2", // }); err != nil { // fmt.Println(err) // return // } // formulaType := excelize.STCellFormulaTypeDataTable // if err := f.SetCellFormula("Sheet1", "C2", "SUM(Table1[[A]:[B]])", // excelize.FormulaOpts{Type: &formulaType}); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } c, _, _, err := ws.prepareCell(cell) if err != nil { return err } f.clearCalcCache() if formula == "" { ws.deleteSharedFormula(c) c.F = nil return f.deleteCalcChain(f.getSheetID(sheet), cell) } if c.F != nil { c.F.Content = formula } else { c.F = &xlsxF{Content: formula} } for _, opt := range opts { if opt.Type != nil { if *opt.Type == STCellFormulaTypeDataTable { return err } c.F.T = *opt.Type if c.F.T == STCellFormulaTypeArray && opt.Ref != nil { if err = ws.setArrayFormula(sheet, &xlsxF{Ref: *opt.Ref, Content: formula}, f.GetDefinedName()); err != nil { return err } } if c.F.T == STCellFormulaTypeShared { ws.deleteSharedFormula(c) if err = ws.setSharedFormula(cell, *opt.Ref); err != nil { return err } } } if opt.Ref != nil { c.F.Ref = *opt.Ref } } c.T, c.IS = "str", nil return err } // setArrayFormula transform the array formula in an array formula range to the // normal formula and set cells in this range to the formula as the normal // formula. func (ws *xlsxWorksheet) setArrayFormula(sheet string, formula *xlsxF, definedNames []DefinedName) error { if len(strings.Split(formula.Ref, ":")) < 2 { return nil } coordinates, err := rangeRefToCoordinates(formula.Ref) if err != nil { return err } _ = sortCoordinates(coordinates) tokens, arrayFormulaOperandTokens, err := getArrayFormulaTokens(sheet, formula.Content, definedNames) if err != nil { return err } topLeftCol, topLeftRow := coordinates[0], coordinates[1] for c := coordinates[0]; c <= coordinates[2]; c++ { for r := coordinates[1]; r <= coordinates[3]; r++ { colOffset, rowOffset := c-topLeftCol, r-topLeftRow for i, af := range arrayFormulaOperandTokens { colNum, rowNum := af.topLeftCol+colOffset, af.topLeftRow+rowOffset if colNum <= af.bottomRightCol && rowNum <= af.bottomRightRow { arrayFormulaOperandTokens[i].targetCellRef, _ = CoordinatesToCellName(colNum, rowNum) } } ws.prepareSheetXML(c, r) if cell := &ws.SheetData.Row[r-1].C[c-1]; cell.f == "" { cell.f = transformArrayFormula(tokens, arrayFormulaOperandTokens) } } } return err } // setArrayFormulaCells transform the array formula in all worksheets to the // normal formula and set cells in the array formula reference range to the // formula as the normal formula. func (f *File) setArrayFormulaCells() error { definedNames := f.GetDefinedName() for _, sheetN := range f.GetSheetList() { ws, err := f.workSheetReader(sheetN) if err != nil { if err.Error() == newNotWorksheetError(sheetN).Error() { continue } return err } for _, row := range ws.SheetData.Row { for _, cell := range row.C { if cell.F != nil && cell.F.T == STCellFormulaTypeArray { if err = ws.setArrayFormula(sheetN, cell.F, definedNames); err != nil { return err } } } } } return nil } // setSharedFormula set shared formula for the cells. func (ws *xlsxWorksheet) setSharedFormula(cell, ref string) error { coordinates, err := rangeRefToCoordinates(ref) if err != nil { return err } _ = sortCoordinates(coordinates) si := ws.countSharedFormula() for col := coordinates[0]; col <= coordinates[2]; col++ { for rol := coordinates[1]; rol <= coordinates[3]; rol++ { ws.prepareSheetXML(col, rol) c := &ws.SheetData.Row[rol-1].C[col-1] if c.F == nil { c.F = &xlsxF{} } c.F.T = STCellFormulaTypeShared if c.R == cell { if c.F.Ref != "" { si = *c.F.Si continue } } c.F.Si = &si } } return err } // countSharedFormula count shared formula in the given worksheet. func (ws *xlsxWorksheet) countSharedFormula() (count int) { for _, row := range ws.SheetData.Row { for _, cell := range row.C { if cell.F != nil && cell.F.Si != nil && *cell.F.Si+1 > count { count = *cell.F.Si + 1 } } } return } // deleteSharedFormula delete shared formula cell from worksheet shared formula // index cache and remove all shared cells formula which refer to the cell which // containing the formula. func (ws *xlsxWorksheet) deleteSharedFormula(c *xlsxC) { if c.F != nil && c.F.Si != nil && c.F.Ref != "" { si := *c.F.Si ws.formulaSI.Delete(si) for r, row := range ws.SheetData.Row { for c, cell := range row.C { if cell.F != nil && cell.F.Si != nil && *cell.F.Si == si && cell.F.Ref == "" { ws.SheetData.Row[r].C[c].F = nil } } } } } // GetCellHyperLink gets a cell hyperlink based on the given worksheet name and // cell reference. If the cell has a hyperlink, it will return 'true' and // the link address, otherwise it will return 'false' and an empty link // address. // // For example, get a hyperlink to a 'H6' cell on a worksheet named 'Sheet1': // // link, target, err := f.GetCellHyperLink("Sheet1", "H6") func (f *File) GetCellHyperLink(sheet, cell string) (bool, string, error) { // Check for correct cell name if _, _, err := SplitCellName(cell); err != nil { return false, "", err } ws, err := f.workSheetReader(sheet) if err != nil { return false, "", err } if ws.Hyperlinks != nil { for _, link := range ws.Hyperlinks.Hyperlink { ok, err := f.checkCellInRangeRef(cell, link.Ref) if err != nil { return false, "", err } if link.Ref == cell || ok { if link.RID != "" { return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err } return true, link.Location, err } } } return false, "", err } // GetHyperLinkCells returns cell references which contain hyperlinks in a // given worksheet name and link type. The optional parameter 'linkType' use for // specific link type, the optional values are "External" for website links, // "Location" for moving to one of cell in this workbook, "None" for no links. // If linkType is empty, it will return all hyperlinks in the worksheet. func (f *File) GetHyperLinkCells(sheet, linkType string) ([]string, error) { var rangeRef []string ws, err := f.workSheetReader(sheet) if err != nil { return rangeRef, err } if ws.Hyperlinks != nil { for _, link := range ws.Hyperlinks.Hyperlink { switch linkType { case "External": if link.RID != "" { rangeRef = append(rangeRef, link.Ref) } case "Location": if link.Location != "" { rangeRef = append(rangeRef, link.Ref) } case "None": return rangeRef, err case "": rangeRef = append(rangeRef, link.Ref) default: return rangeRef, newInvalidLinkTypeError(linkType) } } } return rangeRef, err } // HyperlinkOpts can be passed to SetCellHyperlink to set optional hyperlink // attributes (e.g. display value) type HyperlinkOpts struct { Display *string Tooltip *string } // removeHyperLink remove hyperlink for worksheet and delete relationships for // the worksheet by given sheet name and cell reference. Note that if the cell // in a range reference, the whole hyperlinks will be deleted. func (f *File) removeHyperLink(ws *xlsxWorksheet, sheet, cell string) error { for idx := 0; idx < len(ws.Hyperlinks.Hyperlink); idx++ { link := ws.Hyperlinks.Hyperlink[idx] ok, err := f.checkCellInRangeRef(cell, link.Ref) if err != nil { return err } if link.Ref == cell || ok { ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:idx], ws.Hyperlinks.Hyperlink[idx+1:]...) idx-- f.deleteSheetRelationships(sheet, link.RID) } } if len(ws.Hyperlinks.Hyperlink) == 0 { ws.Hyperlinks = nil } return nil } // SetCellHyperLink provides a function to set cell hyperlink by given // worksheet name and link URL address. LinkType defines three types of // hyperlink "External" for website or "Location" for moving to one of cell in // this workbook or "None" for remove hyperlink. Maximum limit hyperlinks in a // worksheet is 65530. This function is only used to set the hyperlink of the // cell and doesn't affect the value of the cell. If you need to set the value // of the cell, please use the other functions such as `SetCellStyle` or // `SetSheetRow`. The below is example for external link. // // display, tooltip := "https://github.com/xuri/excelize", "Excelize on GitHub" // if err := f.SetCellHyperLink("Sheet1", "A3", // display, "External", excelize.HyperlinkOpts{ // Display: &display, // Tooltip: &tooltip, // }); err != nil { // fmt.Println(err) // } // // Set underline and font color style for the cell. // style, err := f.NewStyle(&excelize.Style{ // Font: &excelize.Font{Color: "1265BE", Underline: "single"}, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "A3", "A3", style) // // This is another example for "Location": // // err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location") func (f *File) SetCellHyperLink(sheet, cell, link, linkType string, opts ...HyperlinkOpts) error { // Check for correct cell name if _, _, err := SplitCellName(cell); err != nil { return err } ws, err := f.workSheetReader(sheet) if err != nil { return err } if cell, err = ws.mergeCellsParser(cell); err != nil { return err } var linkData xlsxHyperlink idx := -1 if ws.Hyperlinks == nil { ws.Hyperlinks = new(xlsxHyperlinks) } for i, hyperlink := range ws.Hyperlinks.Hyperlink { if hyperlink.Ref == cell { idx = i linkData = hyperlink break } } if len(ws.Hyperlinks.Hyperlink) > TotalSheetHyperlinks { return ErrTotalSheetHyperlinks } switch linkType { case "External": sheetPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" rID := f.setRels(linkData.RID, sheetRels, SourceRelationshipHyperLink, link, linkType) linkData = xlsxHyperlink{ Ref: cell, } linkData.RID = "rId" + strconv.Itoa(rID) f.addSheetNameSpace(sheet, SourceRelationship) case "Location": linkData = xlsxHyperlink{ Ref: cell, Location: link, } case "None": return f.removeHyperLink(ws, sheet, cell) default: return newInvalidLinkTypeError(linkType) } for _, o := range opts { if o.Display != nil { linkData.Display = *o.Display } if o.Tooltip != nil { linkData.Tooltip = *o.Tooltip } } if idx == -1 { ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, linkData) return err } ws.Hyperlinks.Hyperlink[idx] = linkData return err } // getCellRichText returns rich text of cell by given string item. func getCellRichText(si *xlsxSI) (runs []RichTextRun) { if si.T != nil { runs = append(runs, RichTextRun{Text: si.T.Val}) } for _, v := range si.R { run := RichTextRun{ Text: v.T.Val, } if v.RPr != nil { run.Font = v.RPr.getFont() } runs = append(runs, run) } return } // GetCellRichText provides a function to get rich text of cell by given // worksheet and cell reference. func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) { ws, err := f.workSheetReader(sheet) if err != nil { return } c, _, _, err := ws.prepareCell(cell) if err != nil { return } if c.T == "inlineStr" && c.IS != nil { runs = getCellRichText(c.IS) return } if c.T != "s" || c.V == "" { return } siIdx, err := strconv.Atoi(c.V) if err != nil { return } sst, err := f.sharedStringsReader() if err != nil { return } if len(sst.SI) <= siIdx || siIdx < 0 { return } runs = getCellRichText(&sst.SI[siIdx]) return } // newRpr create run properties for the rich text by given font format. func (fnt *Font) newRpr() *xlsxRPr { font := fnt.newFont() return &xlsxRPr{ RFont: font.Name, Charset: font.Charset, Family: font.Family, B: font.B, I: font.I, Strike: font.Strike, Outline: font.Outline, Shadow: font.Shadow, Condense: font.Condense, Extend: font.Extend, Color: font.Color, Sz: font.Sz, U: font.U, VertAlign: font.VertAlign, Scheme: font.Scheme, } } // getFont create font format by given internal font properties. func (rPr *xlsxRPr) getFont() *Font { return extractFont(&xlsxFont{ Name: rPr.RFont, Charset: rPr.Charset, Family: rPr.Family, B: rPr.B, I: rPr.I, Strike: rPr.Strike, Outline: rPr.Outline, Shadow: rPr.Shadow, Condense: rPr.Condense, Extend: rPr.Extend, Color: rPr.Color, Sz: rPr.Sz, U: rPr.U, VertAlign: rPr.VertAlign, Scheme: rPr.Scheme, }) } // setRichText provides a function to set rich text of a cell. func setRichText(runs []RichTextRun) ([]xlsxR, error) { var ( textRuns []xlsxR totalCellChars int ) for _, textRun := range runs { totalCellChars += countUTF16String(textRun.Text) if totalCellChars > TotalCellChars { return textRuns, ErrCellCharsLength } run := xlsxR{T: &xlsxT{}} run.T.Val, run.T.Space = trimCellValue(textRun.Text, false) fnt := textRun.Font if fnt != nil { run.RPr = fnt.newRpr() } textRuns = append(textRuns, run) } return textRuns, nil } // SetCellRichText provides a function to set cell with rich text by given // worksheet name, cell reference and rich text runs. For example, set rich text // on the A1 cell of the worksheet named Sheet1: // // package main // // import ( // "fmt" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // if err := f.SetRowHeight("Sheet1", 1, 35); err != nil { // fmt.Println(err) // return // } // if err := f.SetColWidth("Sheet1", "A", "A", 44); err != nil { // fmt.Println(err) // return // } // if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{ // { // Text: "bold", // Font: &excelize.Font{ // Bold: true, // Color: "2354E8", // Family: "Times New Roman", // }, // }, // { // Text: " and ", // Font: &excelize.Font{ // Family: "Times New Roman", // }, // }, // { // Text: "italic ", // Font: &excelize.Font{ // Bold: true, // Color: "E83723", // Italic: true, // Family: "Times New Roman", // }, // }, // { // Text: "text with color and font-family,", // Font: &excelize.Font{ // Bold: true, // Color: "2354E8", // Family: "Times New Roman", // }, // }, // { // Text: "\r\nlarge text with ", // Font: &excelize.Font{ // Size: 14, // Color: "AD23E8", // }, // }, // { // Text: "strike", // Font: &excelize.Font{ // Color: "E89923", // Strike: true, // }, // }, // { // Text: " superscript", // Font: &excelize.Font{ // Color: "DBC21F", // VertAlign: "superscript", // }, // }, // { // Text: " and ", // Font: &excelize.Font{ // Size: 14, // Color: "AD23E8", // VertAlign: "baseline", // }, // }, // { // Text: "underline", // Font: &excelize.Font{ // Color: "23E833", // Underline: "single", // }, // }, // { // Text: " subscript.", // Font: &excelize.Font{ // Color: "017505", // VertAlign: "subscript", // }, // }, // }); err != nil { // fmt.Println(err) // return // } // style, err := f.NewStyle(&excelize.Style{ // Alignment: &excelize.Alignment{ // WrapText: true, // }, // }) // if err != nil { // fmt.Println(err) // return // } // if err := f.SetCellStyle("Sheet1", "A1", "A1", style); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } c, col, row, err := ws.prepareCell(cell) if err != nil { return err } if err := f.sharedStringsLoader(); err != nil { return err } c.S = ws.prepareCellStyle(col, row, c.S) si := xlsxSI{} sst, err := f.sharedStringsReader() if err != nil { return err } if si.R, err = setRichText(runs); err != nil { return err } f.clearCalcCache() for idx, strItem := range sst.SI { if reflect.DeepEqual(strItem, si) { c.T, c.V = "s", strconv.Itoa(idx) return err } } sst.SI = append(sst.SI, si) sst.Count++ sst.UniqueCount++ c.T, c.V = "s", strconv.Itoa(len(sst.SI)-1) return err } // SetSheetRow writes an array to row by given worksheet name, starting // cell reference and a pointer to array type 'slice'. This function is // concurrency safe. For example, writes an array to row 6 start with the cell // B6 on Sheet1: // // err := f.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2}) func (f *File) SetSheetRow(sheet, cell string, slice interface{}) error { return f.setSheetCells(sheet, cell, slice, rows) } // SetSheetCol writes an array to column by given worksheet name, starting // cell reference and a pointer to array type 'slice'. For example, writes an // array to column B start with the cell B6 on Sheet1: // // err := f.SetSheetCol("Sheet1", "B6", &[]interface{}{"1", nil, 2}) func (f *File) SetSheetCol(sheet, cell string, slice interface{}) error { return f.setSheetCells(sheet, cell, slice, columns) } // setSheetCells provides a function to set worksheet cells value. func (f *File) setSheetCells(sheet, cell string, slice interface{}, dir adjustDirection) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } // Make sure 'slice' is a Ptr to Slice v := reflect.ValueOf(slice) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice { return ErrParameterInvalid } v = v.Elem() for i := 0; i < v.Len(); i++ { var cell string var err error if dir == rows { cell, err = CoordinatesToCellName(col+i, row) } else { cell, err = CoordinatesToCellName(col, row+i) } // Error should never happen here. But keep checking to early detect regressions // if it will be introduced in the future. if err != nil { return err } if err := f.SetCellValue(sheet, cell, v.Index(i).Interface()); err != nil { return err } } return err } // getCellInfo does common preparation for all set cell value functions. func (ws *xlsxWorksheet) prepareCell(cell string) (*xlsxC, int, int, error) { var err error cell, err = ws.mergeCellsParser(cell) if err != nil { return nil, 0, 0, err } col, row, err := CellNameToCoordinates(cell) if err != nil { return nil, 0, 0, err } ws.prepareSheetXML(col, row) return &ws.SheetData.Row[row-1].C[col-1], col, row, err } // getCellStringFunc does common value extraction workflow for all get cell // value function. Passed function implements specific part of required // logic. func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return "", err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() cell, err = ws.mergeCellsParser(cell) if err != nil { return "", err } _, row, err := CellNameToCoordinates(cell) if err != nil { return "", err } lastRowNum := 0 if l := len(ws.SheetData.Row); l > 0 { lastRowNum = ws.SheetData.Row[l-1].R } // keep in mind: row starts from 1 if row > lastRowNum { return "", nil } idx, found := sort.Find(len(ws.SheetData.Row), func(i int) int { if ws.SheetData.Row[i].R == row { return 0 } if ws.SheetData.Row[i].R > row { return -1 } return 1 }) if !found { return "", nil } rowData := ws.SheetData.Row[idx] for colIdx := range rowData.C { colData := &rowData.C[colIdx] if cell == colData.R { val, ok, err := fn(ws, colData) if err != nil { return "", err } if ok { return val, nil } break } } return "", nil } // formattedValue provides a function to returns a value after formatted. If // it is possible to apply a format to the cell value, it will do so, if not // then an error will be returned, along with the raw value of the cell. func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, error) { if raw || c.S == 0 { return c.V, nil } styleSheet, err := f.stylesReader() if err != nil { return c.V, err } if styleSheet.CellXfs == nil { return c.V, err } if c.S >= len(styleSheet.CellXfs.Xf) || c.S < 0 { return c.V, err } var numFmtID int if styleSheet.CellXfs.Xf[c.S].NumFmtID != nil { numFmtID = *styleSheet.CellXfs.Xf[c.S].NumFmtID } date1904 := false wb, err := f.workbookReader() if err != nil { return c.V, err } if wb != nil && wb.WorkbookPr != nil { date1904 = wb.WorkbookPr.Date1904 } if fmtCode, ok := styleSheet.getCustomNumFmtCode(numFmtID); ok { return format(c.V, fmtCode, date1904, cellType, f.options), err } if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok { return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err } return c.V, err } // getCustomNumFmtCode provides a function to returns custom number format code. func (ss *xlsxStyleSheet) getCustomNumFmtCode(numFmtID int) (string, bool) { if ss.NumFmts == nil { return "", false } for _, xlsxFmt := range ss.NumFmts.NumFmt { if xlsxFmt.NumFmtID == numFmtID { if xlsxFmt.FormatCode16 != "" { return xlsxFmt.FormatCode16, true } return xlsxFmt.FormatCode, true } } return "", false } // prepareCellStyle provides a function to prepare style index of cell in // worksheet by given column index and style index. func (ws *xlsxWorksheet) prepareCellStyle(col, row, style int) int { if style != 0 { return style } if row <= len(ws.SheetData.Row) { if styleID := ws.SheetData.Row[row-1].S; styleID != 0 { return styleID } } if ws.Cols != nil { for _, c := range ws.Cols.Col { if c.Min <= col && col <= c.Max && c.Style != 0 { return c.Style } } } return style } // mergeCellsParser provides a function to check merged cells in worksheet by // given cell reference. func (ws *xlsxWorksheet) mergeCellsParser(cell string) (string, error) { cell = strings.ToUpper(cell) col, row, err := CellNameToCoordinates(cell) if err != nil { return cell, err } if ws.MergeCells != nil { for i := 0; i < len(ws.MergeCells.Cells); i++ { if ws.MergeCells.Cells[i] == nil { ws.MergeCells.Cells = append(ws.MergeCells.Cells[:i], ws.MergeCells.Cells[i+1:]...) i-- continue } if ref := ws.MergeCells.Cells[i].Ref; len(ws.MergeCells.Cells[i].rect) == 0 && ref != "" { if strings.Count(ref, ":") != 1 { ref += ":" + ref } rect, err := rangeRefToCoordinates(ref) if err != nil { return cell, err } _ = sortCoordinates(rect) ws.MergeCells.Cells[i].rect = rect } if cellInRange([]int{col, row}, ws.MergeCells.Cells[i].rect) { cell = strings.Split(ws.MergeCells.Cells[i].Ref, ":")[0] break } } } return cell, nil } // checkCellInRangeRef provides a function to determine if a given cell reference // in a range. func (f *File) checkCellInRangeRef(cell, rangeRef string) (bool, error) { col, row, err := CellNameToCoordinates(cell) if err != nil { return false, err } if rng := strings.Split(rangeRef, ":"); len(rng) != 2 { return false, err } coordinates, err := rangeRefToCoordinates(rangeRef) if err != nil { return false, err } return cellInRange([]int{col, row}, coordinates), err } // cellInRange provides a function to determine if a given range is within a // range. func cellInRange(cell, ref []int) bool { return cell[0] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= ref[3] } // isOverlap find if the given two rectangles overlap or not. func isOverlap(rect1, rect2 []int) bool { return cellInRange([]int{rect1[0], rect1[1]}, rect2) || cellInRange([]int{rect1[2], rect1[1]}, rect2) || cellInRange([]int{rect1[0], rect1[3]}, rect2) || cellInRange([]int{rect1[2], rect1[3]}, rect2) || cellInRange([]int{rect2[0], rect2[1]}, rect1) || cellInRange([]int{rect2[2], rect2[1]}, rect1) || cellInRange([]int{rect2[0], rect2[3]}, rect1) || cellInRange([]int{rect2[2], rect2[3]}, rect1) } // convertSharedFormula creates a non shared formula from the shared formula // counterpart by given cell reference which not containing the formula. func (c *xlsxC) convertSharedFormula(cell string) (string, error) { col, row, err := CellNameToCoordinates(cell) if err != nil { return "", err } sharedCol, sharedRow, err := CellNameToCoordinates(c.R) if err != nil { return "", err } dCol, dRow := col-sharedCol, row-sharedRow ps := efp.ExcelParser() tokens := ps.Parse(c.F.Content) for i := range tokens { token := tokens[i] if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { tokens[i].TValue = shiftCell(token.TValue, dCol, dRow) } } return ps.Render(), nil } // getSharedFormula find a cell contains the same formula as another cell, // the "shared" value can be used for the t attribute and the si attribute can // be used to refer to the cell containing the formula. Two formulas are // considered to be the same when their respective representations in // R1C1-reference notation, are the same. // // Note that this function not validate ref tag to check the cell whether in // allow range reference, and always return origin shared formula. func getSharedFormula(ws *xlsxWorksheet, si int, cell string) (string, error) { val, ok := ws.formulaSI.Load(si) if ok { return val.(*xlsxC).convertSharedFormula(cell) } for row := range ws.SheetData.Row { r := &ws.SheetData.Row[row] for column := range r.C { c := &r.C[column] if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si != nil && *c.F.Si == si { ws.formulaSI.Store(si, c) return c.convertSharedFormula(cell) } } } return "", nil } // shiftCell returns the cell shifted according to dCol and dRow taking into // consideration absolute references with dollar sign ($) func shiftCell(val string, dCol, dRow int) string { parts := strings.Split(val, ":") for j := 0; j < len(parts); j++ { cell := parts[j] trimmedCellName := strings.ReplaceAll(cell, "$", "") c, r, err := CellNameToCoordinates(trimmedCellName) if err == nil { absCol := strings.Index(cell, "$") == 0 absRow := strings.LastIndex(cell, "$") > 0 if !absCol && !absRow { parts[j], _ = CoordinatesToCellName(c+dCol, r+dRow) } if !absCol && absRow { colName, _ := ColumnNumberToName(c + dCol) parts[j] = colName + "$" + strconv.Itoa(r) } if absCol && !absRow { colName, _ := ColumnNumberToName(c) parts[j] = "$" + colName + strconv.Itoa(r+dRow) } continue } // Cell reference is a column name c, err = ColumnNameToNumber(trimmedCellName) if err == nil && !strings.HasPrefix(cell, "$") { parts[j], _ = ColumnNumberToName(c + dCol) continue } // Cell reference is a row number r, err = strconv.Atoi(trimmedCellName) if err == nil && !strings.HasPrefix(cell, "$") { parts[j] = strconv.Itoa(r + dRow) } } return strings.Join(parts, ":") } ================================================ FILE: cell_test.go ================================================ package excelize import ( "fmt" _ "image/jpeg" "math" "os" "path/filepath" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func TestConcurrency(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) wg := new(sync.WaitGroup) for i := 1; i <= 5; i++ { wg.Add(1) go func(val int, t *testing.T) { // Concurrency set cell value assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val)) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val))) // Concurrency get cell value _, err := f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val)) assert.NoError(t, err) // Concurrency set rows assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{ " Hello", []byte("World"), 42, int8(1<<8/2 - 1), int16(1<<16/2 - 1), int32(1<<32/2 - 1), int64(1<<32/2 - 1), float32(42.65418), -42.65418, float32(42), float64(42), uint(1<<32 - 1), uint8(1<<8 - 1), uint16(1<<16 - 1), uint32(1<<32 - 1), uint64(1<<32 - 1), true, complex64(5 + 10i), })) // Concurrency create style style, err := f.NewStyle(&Style{Font: &Font{Color: "1265BE", Underline: "single"}}) assert.NoError(t, err) // Concurrency set cell style assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style)) // Concurrency get cell style _, err = f.GetCellStyle("Sheet1", "A3") assert.NoError(t, err) // Concurrency add picture assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{ OffsetX: 10, OffsetY: 10, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell", }, )) // Concurrency get cell picture pics, err := f.GetPictures("Sheet1", "A1") assert.Len(t, pics, 0) assert.NoError(t, err) // Concurrency iterate rows rows, err := f.Rows("Sheet1") assert.NoError(t, err) for rows.Next() { _, err := rows.Columns() assert.NoError(t, err) } // Concurrency iterate columns cols, err := f.Cols("Sheet1") assert.NoError(t, err) for cols.Next() { _, err := cols.Rows() assert.NoError(t, err) } // Concurrency set columns style assert.NoError(t, f.SetColStyle("Sheet1", "C:E", style)) // Concurrency get columns style styleID, err := f.GetColStyle("Sheet1", "D") assert.NoError(t, err) assert.Equal(t, style, styleID) // Concurrency set columns width assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 10)) // Concurrency get columns width width, err := f.GetColWidth("Sheet1", "A") assert.NoError(t, err) assert.Equal(t, 10.0, width) // Concurrency set columns visible assert.NoError(t, f.SetColVisible("Sheet1", "A:B", true)) // Concurrency get columns visible visible, err := f.GetColVisible("Sheet1", "A") assert.NoError(t, err) assert.Equal(t, true, visible) // Concurrency add data validation dv := NewDataValidation(true) dv.Sqref = fmt.Sprintf("A%d:B%d", val, val) assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) dv.SetInput(fmt.Sprintf("title:%d", val), strconv.Itoa(val)) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) // Concurrency delete data validation with reference sequence assert.NoError(t, f.DeleteDataValidation("Sheet1", dv.Sqref)) wg.Done() }(i, t) } wg.Wait() val, err := f.GetCellValue("Sheet1", "A1") if err != nil { t.Error(err) } assert.Equal(t, "1", val) // Test the length of data validation dataValidations, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dataValidations, 0) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestConcurrency.xlsx"))) assert.NoError(t, f.Close()) } func TestCheckCellInRangeRef(t *testing.T) { f := NewFile() expectedTrueCellInRangeRefList := [][2]string{ {"c2", "A1:AAZ32"}, {"B9", "A1:B9"}, {"C2", "C2:C2"}, } for _, expectedTrueCellInRangeRef := range expectedTrueCellInRangeRefList { cell := expectedTrueCellInRangeRef[0] reference := expectedTrueCellInRangeRef[1] ok, err := f.checkCellInRangeRef(cell, reference) assert.NoError(t, err) assert.Truef(t, ok, "Expected cell %v to be in range reference %v, got false\n", cell, reference) } expectedFalseCellInRangeRefList := [][2]string{ {"c2", "A4:AAZ32"}, {"C4", "D6:A1"}, // weird case, but you never know {"AEF42", "BZ40:AEF41"}, } for _, expectedFalseCellInRangeRef := range expectedFalseCellInRangeRefList { cell := expectedFalseCellInRangeRef[0] reference := expectedFalseCellInRangeRef[1] ok, err := f.checkCellInRangeRef(cell, reference) assert.NoError(t, err) assert.Falsef(t, ok, "Expected cell %v not to be inside of range reference %v, but got true\n", cell, reference) } ok, err := f.checkCellInRangeRef("A1", "A:B") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) assert.False(t, ok) ok, err = f.checkCellInRangeRef("AA0", "Z0:AB1") assert.Equal(t, newCellNameToCoordinatesError("AA0", newInvalidCellNameError("AA0")), err) assert.False(t, ok) } func TestSetCellFloat(t *testing.T) { sheet := "Sheet1" t.Run("with no decimal", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.0, -1, 64)) assert.NoError(t, f.SetCellFloat(sheet, "A2", 123.0, 1, 64)) val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) assert.Equal(t, "123", val, "A1 should be 123") val, err = f.GetCellValue(sheet, "A2") assert.NoError(t, err) assert.Equal(t, "123", val, "A2 should be 123") }) t.Run("with a decimal and precision limit", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, 1, 64)) val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) assert.Equal(t, "123.4", val, "A1 should be 123.4") }) t.Run("with a decimal and no limit", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, -1, 64)) val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) assert.Equal(t, "123.42", val, "A1 should be 123.42") }) f := NewFile() assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellFloat(sheet, "A", 123.42, -1, 64)) // Test set cell float data type value with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetCellFloat("Sheet:1", "A1", 123.42, -1, 64)) } func TestSetCellUint(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint8(math.MaxUint8))) result, err := f.GetCellValue("Sheet1", "A1") assert.Equal(t, "255", result) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint16))) result, err = f.GetCellValue("Sheet1", "A1") assert.Equal(t, "65535", result) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint32))) result, err = f.GetCellValue("Sheet1", "A1") assert.Equal(t, "4294967295", result) assert.NoError(t, err) // Test uint cell value not exists worksheet assert.EqualError(t, f.SetCellUint("SheetN", "A1", 1), "sheet SheetN does not exist") // Test uint cell value with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellUint("Sheet1", "A", 1)) } func TestSetCellValuesMultiByte(t *testing.T) { f := NewFile() row := []interface{}{ // Test set cell value with multi byte characters value strings.Repeat("\u4E00", TotalCellChars+1), // Test set cell value with XML escape characters strings.Repeat("<>", TotalCellChars/2), strings.Repeat(">", TotalCellChars-1), strings.Repeat(">", TotalCellChars+1), } assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &row)) // Test set cell value with XML escape characters in stream writer _, err := f.NewSheet("Sheet2") assert.NoError(t, err) streamWriter, err := f.NewStreamWriter("Sheet2") assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A1", row)) assert.NoError(t, streamWriter.Flush()) for _, sheetName := range []string{"Sheet1", "Sheet2"} { for cell, expected := range map[string]int{ "A1": TotalCellChars, "B1": TotalCellChars - 1, "C1": TotalCellChars - 1, "D1": TotalCellChars, } { result, err := f.GetCellValue(sheetName, cell) assert.NoError(t, err) assert.Len(t, []rune(result), expected) } } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellValuesMultiByte.xlsx"))) } func TestSetCellValue(t *testing.T) { f := NewFile() assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Now().UTC())) assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Duration(1e13))) // Test set cell value with column and row style inherit style1, err := f.NewStyle(&Style{NumFmt: 2}) assert.NoError(t, err) style2, err := f.NewStyle(&Style{NumFmt: 9}) assert.NoError(t, err) assert.NoError(t, f.SetColStyle("Sheet1", "B", style1)) assert.NoError(t, f.SetRowStyle("Sheet1", 1, 1, style2)) assert.NoError(t, f.SetCellValue("Sheet1", "B1", 0.5)) assert.NoError(t, f.SetCellValue("Sheet1", "B2", 0.5)) B1, err := f.GetCellValue("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "50%", B1) B2, err := f.GetCellValue("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, "0.50", B2) // Test set cell value with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetCellValue("Sheet:1", "A1", "A1")) // Test set cell value with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8") // Test set cell value with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "A1", time.Now().UTC()), "XML syntax error on line 1: invalid UTF-8") // Test set cell value with the shared string table's count not equal with unique count f = NewFile() f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, []byte(fmt.Sprintf(`aa`, NameSpaceSpreadSheet.Value))) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ SheetData: xlsxSheetData{Row: []xlsxRow{ {R: 1, C: []xlsxC{{R: "A1", T: "str", V: "1"}}}, }}, }) assert.NoError(t, f.SetCellValue("Sheet1", "A1", "b")) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "b", val) assert.NoError(t, f.SetCellValue("Sheet1", "B1", "b")) val, err = f.GetCellValue("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "b", val) f = NewFile() // Test set cell value with an IEEE 754 "not-a-number" value or infinity for num, expected := range map[float64]string{ math.NaN(): "NaN", math.Inf(0): "+Inf", math.Inf(-1): "-Inf", } { assert.NoError(t, f.SetCellValue("Sheet1", "A1", num)) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, expected, val) } // Test set cell value with time duration for val, expected := range map[time.Duration]string{ time.Hour*21 + time.Minute*51 + time.Second*44: "21:51:44", time.Hour*21 + time.Minute*50: "21:50", time.Hour*24 + time.Minute*51 + time.Second*44: "24:51:44", time.Hour*24 + time.Minute*50: "24:50:00", } { assert.NoError(t, f.SetCellValue("Sheet1", "A1", val)) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, expected, val) } // Test set cell value with time for val, expected := range map[time.Time]string{ time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC): "Oct-24", time.Date(2024, time.October, 10, 0, 0, 0, 0, time.UTC): "10-10-24", time.Date(2024, time.October, 10, 12, 0, 0, 0, time.UTC): "10/10/24 12:00", } { assert.NoError(t, f.SetCellValue("Sheet1", "A1", val)) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, expected, val) } } func TestSetCellValues(t *testing.T) { f := NewFile() err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC)) assert.NoError(t, err) v, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, v, "12-31-10") // Test date value lower than min date supported by Excel err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC)) assert.NoError(t, err) v, err = f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, v, "1600-12-31T00:00:00Z") } func TestSetCellBool(t *testing.T) { f := NewFile() assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellBool("Sheet1", "A", true)) // Test set cell boolean data type value with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetCellBool("Sheet:1", "A1", true)) } func TestSetCellTime(t *testing.T) { date, err := time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z") assert.NoError(t, err) for location, expected := range map[string]string{ "America/New_York": "40127.75", "Asia/Shanghai": "40128.291666666664", "Europe/London": "40127.958333333336", "UTC": "40127.958333333336", } { timezone, err := time.LoadLocation(location) assert.NoError(t, err) c := &xlsxC{} isNum, err := c.setCellTime(date.In(timezone), false) assert.NoError(t, err) assert.Equal(t, true, isNum) assert.Equal(t, expected, c.V) } } func TestGetCellValue(t *testing.T) { // Test get cell value without r attribute of the row f := NewFile() sheetData := `%s` f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3A4B4A7B7A8B8`))) f.checked = sync.Map{} cells := []string{"A3", "A4", "B4", "A7", "B7"} rows, err := f.GetRows("Sheet1") assert.Equal(t, [][]string{nil, nil, {"A3"}, {"A4", "B4"}, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows) assert.NoError(t, err) for _, cell := range cells { value, err := f.GetCellValue("Sheet1", cell) assert.Equal(t, cell, value) assert.NoError(t, err) } cols, err := f.GetCols("Sheet1") assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2B2`))) f.checked = sync.Map{} cell, err := f.GetCellValue("Sheet1", "A2") assert.Equal(t, "A2", cell) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2B2`))) f.checked = sync.Map{} rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1B1`))) f.checked = sync.Map{} rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{{"A1", "B1"}}, rows) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3A4B4A7B7A8B8`))) f.checked = sync.Map{} rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `H6r0A6F4A6B6C6100B3`))) f.checked = sync.Map{} cell, err = f.GetCellValue("Sheet1", "H6") assert.Equal(t, "H6", cell) assert.NoError(t, err) rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{ {"A6", "B6", "C6"}, nil, {"100", "B3"}, {"", "", "", "", "", "F4"}, nil, {"", "", "", "", "", "", "", "H6"}, }, rows) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1A3`))) f.checked = sync.Map{} rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{{"A1"}, nil, {"A3"}}, rows) assert.NoError(t, err) cell, err = f.GetCellValue("Sheet1", "A3") assert.Equal(t, "A3", cell) assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, ` 2422.3000000000002 2422.3000000000002 12.4 964 1101.5999999999999 275.39999999999998 68.900000000000006 44385.208333333336 5.0999999999999996 5.1100000000000003 5.0999999999999996 5.1109999999999998 5.1111000000000004 2422.012345678 2422.0123456789 12.012345678901 964 1101.5999999999999 275.39999999999998 68.900000000000006 8.8880000000000001E-2 4.0000000000000003e-5 2422.3000000000002 1101.5999999999999 275.39999999999998 68.900000000000006 1.1000000000000001 1234567890123_4 123456789_0123_4 +0.0000000000000000002399999999999992E-4 7.2399999999999992E-2 20200208T080910.123 20200208T080910,123 20221022T150529Z 2022-10-22T15:05:29Z 2020-07-10 15:00:00.000`))) f.checked = sync.Map{} rows, err = f.GetCols("Sheet1") assert.Equal(t, []string{ "2422.3", "2422.3", "12.4", "964", "1101.6", "275.4", "68.9", "44385.2083333333", "5.1", "5.11", "5.1", "5.111", "5.1111", "2422.012345678", "2422.0123456789", "12.012345678901", "964", "1101.6", "275.4", "68.9", "0.08888", "0.00004", "2422.3", "1101.6", "275.4", "68.9", "1.1", "1234567890123_4", "123456789_0123_4", "2.39999999999999E-23", "0.0724", "43869.3397004977", "43869.3397004977", "44856.6288078704", "44856.6288078704", "2020-07-10 15:00:00.000", }, rows[0]) assert.NoError(t, err) // Test get cell value with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, value := f.GetCellValue("Sheet1", "A1") assert.EqualError(t, value, "XML syntax error on line 1: invalid UTF-8") // Test get cell value with invalid sheet name _, err = f.GetCellValue("Sheet:1", "A1") assert.Equal(t, ErrSheetNameInvalid, err) } func TestGetCellType(t *testing.T) { f := NewFile() cellType, err := f.GetCellType("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, CellTypeUnset, cellType) assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1")) cellType, err = f.GetCellType("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, CellTypeSharedString, cellType) _, err = f.GetCellType("Sheet1", "A") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) // Test get cell type with invalid sheet name _, err = f.GetCellType("Sheet:1", "A1") assert.Equal(t, ErrSheetNameInvalid, err) } func TestGetValueFrom(t *testing.T) { f := NewFile() c := xlsxC{T: "s"} sst, err := f.sharedStringsReader() assert.NoError(t, err) value, err := c.getValueFrom(f, sst, false) assert.NoError(t, err) assert.Empty(t, value) c = xlsxC{T: "s", V: " 1 "} value, err = c.getValueFrom(f, &xlsxSST{Count: 1, SI: []xlsxSI{{}, {T: &xlsxT{Val: "s"}}}}, false) assert.NoError(t, err) assert.Equal(t, "s", value) } func TestGetCellFormula(t *testing.T) { // Test get cell formula on not exist worksheet f := NewFile() _, err := f.GetCellFormula("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get cell formula with invalid sheet name _, err = f.GetCellFormula("Sheet:1", "A1") assert.Equal(t, ErrSheetNameInvalid, err) // Test get cell formula on no formula cell assert.NoError(t, f.SetCellValue("Sheet1", "A1", true)) _, err = f.GetCellFormula("Sheet1", "A1") assert.NoError(t, err) // Test get cell shared formula f = NewFile() sheetData := `12*A12%s34567` for sharedFormula, expected := range map[string]string{ `2*A2`: `2*A3`, `2*A1A`: `2*A1A`, `2*$A$2+LEN("")`: `2*$A$2+LEN("")`, `SUMIF(A:A,$B11, 5:5)`: `SUMIF(A:A,$B12,6:6)`, `SUMIF(A:A,B$11, 5:5)`: `SUMIF(A:A,B$11,6:6)`, } { f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, sharedFormula))) formula, err := f.GetCellFormula("Sheet1", "B3") assert.NoError(t, err) assert.Equal(t, expected, formula) // Test get shared formula form cache formula, err = f.GetCellFormula("Sheet1", "B3") assert.NoError(t, err) assert.Equal(t, expected, formula) } f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) formula, err := f.GetCellFormula("Sheet1", "B2") assert.NoError(t, err) assert.Empty(t, formula) // Test get array formula with invalid cell range reference f = NewFile() assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line})) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) formulaType, ref := STCellFormulaTypeArray, "B1:B2" assert.NoError(t, f.SetCellFormula("Sheet2", "B1", "A1:B2", FormulaOpts{Ref: &ref, Type: &formulaType})) ws, ok := f.Sheet.Load("xl/worksheets/sheet3.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[1].F.Ref = ":" _, err = f.getCellFormula("Sheet2", "A1", true) assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err) // Test set formula for the cells in array formula range with unsupported charset f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.setArrayFormulaCells(), "XML syntax error on line 1: invalid UTF-8") // Test get shared formula after updated refer cell formula, the shared // formula cell reference range covered the previous. f = NewFile() formulaType, ref = STCellFormulaTypeShared, "C2:C6" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType})) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "A2+B2", formula) formula, err = f.GetCellFormula("Sheet1", "C6") assert.NoError(t, err) assert.Equal(t, "A6+B6", formula) formulaType, ref = STCellFormulaTypeShared, "C2:C8" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType})) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "A2*B2", formula) formula, err = f.GetCellFormula("Sheet1", "C8") assert.NoError(t, err) assert.Equal(t, "A8*B8", formula) assert.NoError(t, f.Close()) // Test get shared formula after updated refer cell formula, the shared // formula cell reference range not over the previous. f = NewFile() formulaType, ref = STCellFormulaTypeShared, "C2:C6" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType})) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "A2+B2", formula) formula, err = f.GetCellFormula("Sheet1", "C6") assert.NoError(t, err) assert.Equal(t, "A6+B6", formula) formulaType, ref = STCellFormulaTypeShared, "C2:C4" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType})) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "A2*B2", formula) formula, err = f.GetCellFormula("Sheet1", "C6") assert.NoError(t, err) assert.Empty(t, formula) // Test get shared formula after remove refer cell formula f = NewFile() formulaType, ref = STCellFormulaTypeShared, "C2:C6" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "")) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Empty(t, formula) formula, err = f.GetCellFormula("Sheet1", "C6") assert.NoError(t, err) assert.Empty(t, formula) formulaType, ref = STCellFormulaTypeShared, "C2:C8" assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType})) formula, err = f.GetCellFormula("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, "A2*B2", formula) formula, err = f.GetCellFormula("Sheet1", "C8") assert.NoError(t, err) assert.Equal(t, "A8*B8", formula) assert.NoError(t, f.Close()) } func TestConvertSharedFormula(t *testing.T) { c := xlsxC{R: "A"} _, err := c.convertSharedFormula("A") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) _, err = c.convertSharedFormula("A1") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) } func ExampleFile_SetCellFloat() { f := NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() x := 3.14159265 if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil { fmt.Println(err) } val, err := f.GetCellValue("Sheet1", "A1") if err != nil { fmt.Println(err) return } fmt.Println(val) // Output: 3.14 } func BenchmarkSetCellValue(b *testing.B) { values := []string{"First", "Second", "Third", "Fourth", "Fifth", "Sixth"} cols := []string{"A", "B", "C", "D", "E", "F"} f := NewFile() b.ResetTimer() for i := 1; i <= b.N; i++ { for j := 0; j < len(values); j++ { if err := f.SetCellValue("Sheet1", cols[j]+strconv.Itoa(i), values[j]); err != nil { b.Error(err) } } } } func TestOverflowNumericCell(t *testing.T) { f, err := OpenFile(filepath.Join("test", "OverflowNumericCell.xlsx")) if !assert.NoError(t, err) { t.FailNow() } val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) // GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648" assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225") assert.NoError(t, f.Close()) } func TestSetCellFormula(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.SetCellFormula("Sheet1", "B19", "SUM(Sheet2!D2,Sheet2!D11)")) assert.NoError(t, f.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)")) // Test set cell formula with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetCellFormula("Sheet:1", "A1", "SUM(1,2)")) // Test set cell formula with illegal rows number assert.Equal(t, newCellNameToCoordinatesError("C", newInvalidCellNameError("C")), f.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx"))) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "CalcChain.xlsx")) if !assert.NoError(t, err) { t.FailNow() } // Test remove cell formula assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula2.xlsx"))) // Test remove all cell formula assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula3.xlsx"))) assert.NoError(t, f.Close()) // Test set shared formula for the cells f = NewFile() for r := 1; r <= 5; r++ { assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", r), &[]interface{}{r, r + 1})) } formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) sharedFormulaSpreadsheet := filepath.Join("test", "TestSetCellFormula4.xlsx") assert.NoError(t, f.SaveAs(sharedFormulaSpreadsheet)) f, err = OpenFile(sharedFormulaSpreadsheet) assert.NoError(t, err) ref = "D1:D5" assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType})) ref = "" assert.Equal(t, ErrParameterInvalid, f.SetCellFormula("Sheet1", "D1", "A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula5.xlsx"))) // Test set table formula for the cells f = NewFile() for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row)) } assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:C2", Name: "Table1", StyleName: "TableStyleMedium2"})) formulaType = STCellFormulaTypeDataTable assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx"))) // Test set array formula with invalid cell range reference formulaType, ref = STCellFormulaTypeArray, ":" assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &ref, Type: &formulaType})) // Test set array formula with invalid cell reference formulaType, ref = STCellFormulaTypeArray, "A1:A2" assert.Equal(t, ErrColumnNumber, f.SetCellFormula("Sheet1", "A1", "SUM(XFE1:XFE2)", FormulaOpts{Ref: &ref, Type: &formulaType})) } func TestGetCellRichText(t *testing.T) { f, theme := NewFile(), 1 runsSource := []RichTextRun{ { Text: "a\n", }, { Text: "b", Font: &Font{ Underline: "single", Color: "ff0000", ColorTheme: &theme, ColorTint: 0.5, Bold: true, Italic: true, Family: "Times New Roman", Size: 100, Strike: true, }, }, } assert.NoError(t, f.SetCellRichText("Sheet1", "A1", runsSource)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", false)) runs, err := f.GetCellRichText("Sheet1", "A2") assert.NoError(t, err) assert.Equal(t, []RichTextRun(nil), runs) runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, runsSource[0].Text, runs[0].Text) assert.Nil(t, runs[0].Font) assert.NotNil(t, runs[1].Font) runsSource[1].Font.Color = strings.ToUpper(runsSource[1].Font.Color) assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font") // Test get cell rich text with inlineStr ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{ T: "inlineStr", IS: &xlsxSI{ T: &xlsxT{Val: "A"}, R: []xlsxR{{T: &xlsxT{Val: "1"}}}, }, } runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, []RichTextRun{{Text: "A"}, {Text: "1"}}, runs) // Test get cell rich text when string item index overflow ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{V: "2", IS: &xlsxSI{}} runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) // Test get cell rich text when string item index is negative ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{T: "s", V: "-1", IS: &xlsxSI{}} runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) // Test get cell rich text when string item index is invalid ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{T: "s", V: "A", IS: &xlsxSI{}} runs, err = f.GetCellRichText("Sheet1", "A1") assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax") assert.Equal(t, 0, len(runs)) // Test get cell rich text on invalid string item index ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{V: "x"} runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) // Test set cell rich text on not exists worksheet _, err = f.GetCellRichText("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN does not exist") // Test set cell rich text with illegal cell reference _, err = f.GetCellRichText("Sheet1", "A") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) // Test set rich text color theme without tint assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTheme: &theme}}})) // Test set rich text color tint without theme assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTint: 0.5}}})) // Test set cell rich text with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", runsSource), "XML syntax error on line 1: invalid UTF-8") // Test get cell rich text with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err = f.GetCellRichText("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get cell rich text with invalid sheet name _, err = f.GetCellRichText("Sheet:1", "A1") assert.Equal(t, ErrSheetNameInvalid, err) } func TestSetCellRichText(t *testing.T) { f := NewFile() assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35)) richTextRun := []RichTextRun{ { Text: "bold", Font: &Font{ Bold: true, Color: "2354E8", ColorIndexed: 0, Family: "Times New Roman", }, }, { Text: " and ", Font: &Font{ Family: "Times New Roman", }, }, { Text: "italic ", Font: &Font{ Bold: true, Color: "E83723", Italic: true, Family: "Times New Roman", }, }, { Text: "text with color and font-family, ", Font: &Font{ Bold: true, Color: "2354E8", Family: "Times New Roman", }, }, { Text: "\r\nlarge text with ", Font: &Font{ Size: 14, Color: "AD23E8", }, }, { Text: "strike", Font: &Font{ Color: "E89923", Strike: true, }, }, { Text: " superscript", Font: &Font{ Color: "DBC21F", VertAlign: "superscript", }, }, { Text: " and ", Font: &Font{ Size: 14, Color: "AD23E8", VertAlign: "baseline", }, }, { Text: "underline", Font: &Font{ Color: "23E833", Underline: "single", }, }, { Text: " subscript.", Font: &Font{ Color: "017505", VertAlign: "subscript", }, }, } assert.NoError(t, f.SetCellRichText("Sheet1", "A1", richTextRun)) assert.NoError(t, f.SetCellRichText("Sheet1", "A2", richTextRun)) assert.NoError(t, f.SetCellRichText("Sheet1", "A3", []RichTextRun{{Text: strings.Repeat("\u4e00", TotalCellChars)}})) style, err := f.NewStyle(&Style{ Alignment: &Alignment{ WrapText: true, Vertical: "center", }, }) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) assert.NoError(t, f.AutoFitColWidth("Sheet1", "A")) runs, err := f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, richTextRun, runs) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx"))) // Test set cell rich text on not exists worksheet assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN does not exist") // Test set cell rich text with invalid sheet name assert.EqualError(t, f.SetCellRichText("Sheet:1", "A1", richTextRun), ErrSheetNameInvalid.Error()) // Test set cell rich text with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellRichText("Sheet1", "A", richTextRun)) richTextRun = []RichTextRun{{Text: strings.Repeat("s", TotalCellChars+1)}} // Test set cell rich text with characters over the maximum limit assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", richTextRun), ErrCellCharsLength.Error()) } func TestFormattedValue(t *testing.T) { f := NewFile() result, err := f.formattedValue(&xlsxC{S: 0, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) // S is too large result, err = f.formattedValue(&xlsxC{S: 15, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) // S is too small result, err = f.formattedValue(&xlsxC{S: -15, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) customNumFmt := "[$-409]MM/DD/YYYY" _, err = f.NewStyle(&Style{ CustomNumFmt: &customNumFmt, }) assert.NoError(t, err) result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "03/04/2019", result) // Test format value with no built-in number format ID numFmtID := 5 f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: &numFmtID, }) result, err = f.formattedValue(&xlsxC{S: 2, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) // Test format value with invalid number format ID f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: nil, }) result, err = f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) // Test format value with empty number format f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: &numFmtID, }) result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) // Test format numeric value with shared string data type f.Styles.NumFmts, numFmtID = nil, 11 f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: &numFmtID, }) result, err = f.formattedValue(&xlsxC{S: 5, V: "43528"}, false, CellTypeSharedString) assert.NoError(t, err) assert.Equal(t, "43528", result) // Test format decimal value with build-in number format ID styleID, err := f.NewStyle(&Style{ NumFmt: 1, }) assert.NoError(t, err) result, err = f.formattedValue(&xlsxC{S: styleID, V: "310.56"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "311", result) assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber, nil)) // Test format value with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test format value with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.Equal(t, "text", format("text", "0", false, CellTypeNumber, nil)) } func TestFormattedValueNilXfs(t *testing.T) { // Set the CellXfs to nil and verify that the formattedValue function does not crash f := NewFile() f.Styles.CellXfs = nil result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) } func TestFormattedValueNilNumFmts(t *testing.T) { // Set the NumFmts value to nil and verify that the formattedValue function does not crash f := NewFile() f.Styles.NumFmts = nil result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) } func TestFormattedValueNilWorkbook(t *testing.T) { // Set the Workbook value to nil and verify that the formattedValue function does not crash f := NewFile() f.WorkBook = nil result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) } func TestFormattedValueNilWorkbookPr(t *testing.T) { // Set the WorkBook.WorkbookPr value to nil and verify that the formattedValue function does not // crash. f := NewFile() f.WorkBook.WorkbookPr = nil result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber) assert.NoError(t, err) assert.Equal(t, "43528", result) } func TestGetCustomNumFmtCode(t *testing.T) { expected := "[$-ja-JP-x-gannen,80]ggge\"年\"m\"月\"d\"日\";@" styleSheet := &xlsxStyleSheet{NumFmts: &xlsxNumFmts{NumFmt: []*xlsxNumFmt{ {NumFmtID: 164, FormatCode16: expected}, }}} numFmtCode, ok := styleSheet.getCustomNumFmtCode(164) assert.Equal(t, expected, numFmtCode) assert.True(t, ok) } func TestSharedStringsError(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) tempFile, ok := f.tempFiles.Load(defaultXMLPathSharedStrings) assert.True(t, ok) f.tempFiles.Store(defaultXMLPathSharedStrings, "") assert.Equal(t, "1", f.getFromStringItem(1)) // Test get from string item with invalid offset range f.sharedStringItem = [][]uint{{0}} assert.Equal(t, "0", f.getFromStringItem(0)) // Cleanup undelete temporary files assert.NoError(t, os.Remove(tempFile.(string))) // Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows err = f.SetCellValue("Sheet1", "A19", "A19") assert.Error(t, err) f.tempFiles.Store(defaultXMLPathSharedStrings, "") err = f.SetCellRichText("Sheet1", "A19", []RichTextRun{}) assert.Error(t, err) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) rows, err := f.Rows("Sheet1") assert.NoError(t, err) const maxUint16 = 1<<16 - 1 currentRow := 0 for rows.Next() { currentRow++ if currentRow == 19 { _, err := rows.Columns() assert.NoError(t, err) // Test get cell value from string item with invalid offset f.sharedStringItem[1] = []uint{maxUint16 - 1, maxUint16} assert.Equal(t, "1", f.getFromStringItem(1)) break } } assert.NoError(t, rows.Close()) // Test shared string item temporary files has been closed before close the workbook assert.NoError(t, f.sharedStringTemp.Close()) assert.Error(t, f.Close()) // Cleanup undelete temporary files f.tempFiles.Range(func(k, v interface{}) bool { return assert.NoError(t, os.Remove(v.(string))) }) f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) rows, err = f.Rows("Sheet1") assert.NoError(t, err) currentRow = 0 for rows.Next() { currentRow++ if currentRow == 19 { _, err := rows.Columns() assert.NoError(t, err) break } } assert.NoError(t, rows.Close()) assert.NoError(t, f.sharedStringTemp.Close()) // Test shared string item temporary files has been closed before set the cell value assert.Error(t, f.SetCellValue("Sheet1", "A1", "A1")) assert.Error(t, f.Close()) // Cleanup undelete temporary files f.tempFiles.Range(func(k, v interface{}) bool { return assert.NoError(t, os.Remove(v.(string))) }) } func TestSetCellIntFunc(t *testing.T) { cases := []struct { val interface{} target string }{ {val: 128, target: "128"}, {val: int8(-128), target: "-128"}, {val: int16(-32768), target: "-32768"}, {val: int32(-2147483648), target: "-2147483648"}, {val: int64(-9223372036854775808), target: "-9223372036854775808"}, {val: uint(128), target: "128"}, {val: uint8(255), target: "255"}, {val: uint16(65535), target: "65535"}, {val: uint32(4294967295), target: "4294967295"}, {val: uint64(18446744073709551615), target: "18446744073709551615"}, } for _, c := range cases { cell := &xlsxC{} setCellIntFunc(cell, c.val) assert.Equal(t, c.target, cell.V) } } func TestSIString(t *testing.T) { assert.Empty(t, xlsxSI{}.String()) } func TestGetCellStringFunc(t *testing.T) { f := NewFile() ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row = []xlsxRow{{R: 2}} val, err := f.GetCellValue("Sheet1", "A1") assert.Empty(t, val) assert.NoError(t, err) assert.NoError(t, f.Close()) } ================================================ FILE: chart.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "fmt" "strconv" "strings" ) // ChartType is the type of supported chart types. type ChartType byte // This section defines the currently supported chart types enumeration. const ( Area ChartType = iota AreaStacked AreaPercentStacked Area3D Area3DStacked Area3DPercentStacked Bar BarStacked BarPercentStacked Bar3DClustered Bar3DStacked Bar3DPercentStacked Bar3DConeClustered Bar3DConeStacked Bar3DConePercentStacked Bar3DPyramidClustered Bar3DPyramidStacked Bar3DPyramidPercentStacked Bar3DCylinderClustered Bar3DCylinderStacked Bar3DCylinderPercentStacked Col ColStacked ColPercentStacked Col3D Col3DClustered Col3DStacked Col3DPercentStacked Col3DCone Col3DConeClustered Col3DConeStacked Col3DConePercentStacked Col3DPyramid Col3DPyramidClustered Col3DPyramidStacked Col3DPyramidPercentStacked Col3DCylinder Col3DCylinderClustered Col3DCylinderStacked Col3DCylinderPercentStacked Doughnut Line Line3D Pie Pie3D PieOfPie BarOfPie Radar Scatter Surface3D WireframeSurface3D Contour WireframeContour Bubble Bubble3D StockHighLowClose StockOpenHighLowClose ) // ChartDashType is the type of supported chart dash types. type ChartDashType byte // This section defines the currently supported chart dash types enumeration. const ( ChartDashUnset ChartDashType = iota ChartDashSolid ChartDashDot ChartDashDash ChartDashLgDash ChartDashSashDot ChartDashLgDashDot ChartDashLgDashDotDot ChartDashSysDash ChartDashSysDot ChartDashSysDashDot ChartDashSysDashDotDot ) // ChartLineType is the type of supported chart line types. type ChartLineType byte // This section defines the currently supported chart line types enumeration. const ( ChartLineUnset ChartLineType = iota ChartLineSolid ChartLineNone ChartLineAutomatic ) // ChartTickLabelPositionType is the type of supported chart tick label position // types. type ChartTickLabelPositionType byte // This section defines the supported chart tick label position types // enumeration. const ( ChartTickLabelNextToAxis ChartTickLabelPositionType = iota ChartTickLabelHigh ChartTickLabelLow ChartTickLabelNone ) // This section defines the default value of chart properties. var ( chartView3DRotX = map[ChartType]int{ Area: 0, AreaStacked: 0, AreaPercentStacked: 0, Area3D: 15, Area3DStacked: 15, Area3DPercentStacked: 15, Bar: 0, BarStacked: 0, BarPercentStacked: 0, Bar3DClustered: 15, Bar3DStacked: 15, Bar3DPercentStacked: 15, Bar3DConeClustered: 15, Bar3DConeStacked: 15, Bar3DConePercentStacked: 15, Bar3DPyramidClustered: 15, Bar3DPyramidStacked: 15, Bar3DPyramidPercentStacked: 15, Bar3DCylinderClustered: 15, Bar3DCylinderStacked: 15, Bar3DCylinderPercentStacked: 15, Col: 0, ColStacked: 0, ColPercentStacked: 0, Col3D: 15, Col3DClustered: 15, Col3DStacked: 15, Col3DPercentStacked: 15, Col3DCone: 15, Col3DConeClustered: 15, Col3DConeStacked: 15, Col3DConePercentStacked: 15, Col3DPyramid: 15, Col3DPyramidClustered: 15, Col3DPyramidStacked: 15, Col3DPyramidPercentStacked: 15, Col3DCylinder: 15, Col3DCylinderClustered: 15, Col3DCylinderStacked: 15, Col3DCylinderPercentStacked: 15, Doughnut: 0, Line: 0, Line3D: 20, Pie: 0, Pie3D: 30, PieOfPie: 0, BarOfPie: 0, Radar: 0, Scatter: 0, Surface3D: 15, WireframeSurface3D: 15, Contour: 90, WireframeContour: 90, } chartView3DRotY = map[ChartType]int{ Area: 0, AreaStacked: 0, AreaPercentStacked: 0, Area3D: 20, Area3DStacked: 20, Area3DPercentStacked: 20, Bar: 0, BarStacked: 0, BarPercentStacked: 0, Bar3DClustered: 20, Bar3DStacked: 20, Bar3DPercentStacked: 20, Bar3DConeClustered: 20, Bar3DConeStacked: 20, Bar3DConePercentStacked: 20, Bar3DPyramidClustered: 20, Bar3DPyramidStacked: 20, Bar3DPyramidPercentStacked: 20, Bar3DCylinderClustered: 20, Bar3DCylinderStacked: 20, Bar3DCylinderPercentStacked: 20, Col: 0, ColStacked: 0, ColPercentStacked: 0, Col3D: 20, Col3DClustered: 20, Col3DStacked: 20, Col3DPercentStacked: 20, Col3DCone: 20, Col3DConeClustered: 20, Col3DConeStacked: 20, Col3DConePercentStacked: 20, Col3DPyramid: 20, Col3DPyramidClustered: 20, Col3DPyramidStacked: 20, Col3DPyramidPercentStacked: 20, Col3DCylinder: 20, Col3DCylinderClustered: 20, Col3DCylinderStacked: 20, Col3DCylinderPercentStacked: 20, Doughnut: 0, Line: 0, Line3D: 15, Pie: 0, Pie3D: 0, PieOfPie: 0, BarOfPie: 0, Radar: 0, Scatter: 0, Surface3D: 20, WireframeSurface3D: 20, Contour: 0, WireframeContour: 0, } plotAreaChartOverlap = map[ChartType]int{ BarStacked: 100, BarPercentStacked: 100, ColStacked: 100, ColPercentStacked: 100, } chartView3DPerspective = map[ChartType]int{ Line3D: 30, Contour: 0, WireframeContour: 0, } chartView3DRAngAx = map[ChartType]int{ Area: 0, AreaStacked: 0, AreaPercentStacked: 0, Area3D: 1, Area3DStacked: 1, Area3DPercentStacked: 1, Bar: 0, BarStacked: 0, BarPercentStacked: 0, Bar3DClustered: 1, Bar3DStacked: 1, Bar3DPercentStacked: 1, Bar3DConeClustered: 1, Bar3DConeStacked: 1, Bar3DConePercentStacked: 1, Bar3DPyramidClustered: 1, Bar3DPyramidStacked: 1, Bar3DPyramidPercentStacked: 1, Bar3DCylinderClustered: 1, Bar3DCylinderStacked: 1, Bar3DCylinderPercentStacked: 1, Col: 0, ColStacked: 0, ColPercentStacked: 0, Col3D: 1, Col3DClustered: 1, Col3DStacked: 1, Col3DPercentStacked: 1, Col3DCone: 1, Col3DConeClustered: 1, Col3DConeStacked: 1, Col3DConePercentStacked: 1, Col3DPyramid: 1, Col3DPyramidClustered: 1, Col3DPyramidStacked: 1, Col3DPyramidPercentStacked: 1, Col3DCylinder: 1, Col3DCylinderClustered: 1, Col3DCylinderStacked: 1, Col3DCylinderPercentStacked: 1, Doughnut: 0, Line: 0, Line3D: 0, Pie: 0, Pie3D: 0, PieOfPie: 0, BarOfPie: 0, Radar: 0, Scatter: 0, Surface3D: 0, WireframeSurface3D: 0, Contour: 0, Bubble: 0, Bubble3D: 0, } chartLegendPosition = map[string]string{ "bottom": "b", "left": "l", "right": "r", "top": "t", "top_right": "tr", } chartValAxNumFmtFormatCode = map[ChartType]string{ Area: "General", AreaStacked: "General", AreaPercentStacked: "0%", Area3D: "General", Area3DStacked: "General", Area3DPercentStacked: "0%", Bar: "General", BarStacked: "General", BarPercentStacked: "0%", Bar3DClustered: "General", Bar3DStacked: "General", Bar3DPercentStacked: "0%", Bar3DConeClustered: "General", Bar3DConeStacked: "General", Bar3DConePercentStacked: "0%", Bar3DPyramidClustered: "General", Bar3DPyramidStacked: "General", Bar3DPyramidPercentStacked: "0%", Bar3DCylinderClustered: "General", Bar3DCylinderStacked: "General", Bar3DCylinderPercentStacked: "0%", Col: "General", ColStacked: "General", ColPercentStacked: "0%", Col3D: "General", Col3DClustered: "General", Col3DStacked: "General", Col3DPercentStacked: "0%", Col3DCone: "General", Col3DConeClustered: "General", Col3DConeStacked: "General", Col3DConePercentStacked: "0%", Col3DPyramid: "General", Col3DPyramidClustered: "General", Col3DPyramidStacked: "General", Col3DPyramidPercentStacked: "0%", Col3DCylinder: "General", Col3DCylinderClustered: "General", Col3DCylinderStacked: "General", Col3DCylinderPercentStacked: "0%", Doughnut: "General", Line: "General", Line3D: "General", Pie: "General", Pie3D: "General", PieOfPie: "General", BarOfPie: "General", Radar: "General", Scatter: "General", Surface3D: "General", WireframeSurface3D: "General", Contour: "General", WireframeContour: "General", Bubble: "General", Bubble3D: "General", StockHighLowClose: "General", StockOpenHighLowClose: "General", } chartValAxCrossBetween = map[ChartType]string{ Area: "midCat", AreaStacked: "midCat", AreaPercentStacked: "midCat", Area3D: "midCat", Area3DStacked: "midCat", Area3DPercentStacked: "midCat", Bar: "between", BarStacked: "between", BarPercentStacked: "between", Bar3DClustered: "between", Bar3DStacked: "between", Bar3DPercentStacked: "between", Bar3DConeClustered: "between", Bar3DConeStacked: "between", Bar3DConePercentStacked: "between", Bar3DPyramidClustered: "between", Bar3DPyramidStacked: "between", Bar3DPyramidPercentStacked: "between", Bar3DCylinderClustered: "between", Bar3DCylinderStacked: "between", Bar3DCylinderPercentStacked: "between", Col: "between", ColStacked: "between", ColPercentStacked: "between", Col3D: "between", Col3DClustered: "between", Col3DStacked: "between", Col3DPercentStacked: "between", Col3DCone: "between", Col3DConeClustered: "between", Col3DConeStacked: "between", Col3DConePercentStacked: "between", Col3DPyramid: "between", Col3DPyramidClustered: "between", Col3DPyramidStacked: "between", Col3DPyramidPercentStacked: "between", Col3DCylinder: "between", Col3DCylinderClustered: "between", Col3DCylinderStacked: "between", Col3DCylinderPercentStacked: "between", Doughnut: "between", Line: "between", Line3D: "between", Pie: "between", Pie3D: "between", PieOfPie: "between", BarOfPie: "between", Radar: "between", Scatter: "between", Surface3D: "midCat", WireframeSurface3D: "midCat", Contour: "midCat", WireframeContour: "midCat", Bubble: "midCat", Bubble3D: "midCat", StockHighLowClose: "between", StockOpenHighLowClose: "between", } plotAreaChartGrouping = map[ChartType]string{ Area: "standard", AreaStacked: "stacked", AreaPercentStacked: "percentStacked", Area3D: "standard", Area3DStacked: "stacked", Area3DPercentStacked: "percentStacked", Bar: "clustered", BarStacked: "stacked", BarPercentStacked: "percentStacked", Bar3DClustered: "clustered", Bar3DStacked: "stacked", Bar3DPercentStacked: "percentStacked", Bar3DConeClustered: "clustered", Bar3DConeStacked: "stacked", Bar3DConePercentStacked: "percentStacked", Bar3DPyramidClustered: "clustered", Bar3DPyramidStacked: "stacked", Bar3DPyramidPercentStacked: "percentStacked", Bar3DCylinderClustered: "clustered", Bar3DCylinderStacked: "stacked", Bar3DCylinderPercentStacked: "percentStacked", Col: "clustered", ColStacked: "stacked", ColPercentStacked: "percentStacked", Col3D: "standard", Col3DClustered: "clustered", Col3DStacked: "stacked", Col3DPercentStacked: "percentStacked", Col3DCone: "standard", Col3DConeClustered: "clustered", Col3DConeStacked: "stacked", Col3DConePercentStacked: "percentStacked", Col3DPyramid: "standard", Col3DPyramidClustered: "clustered", Col3DPyramidStacked: "stacked", Col3DPyramidPercentStacked: "percentStacked", Col3DCylinder: "standard", Col3DCylinderClustered: "clustered", Col3DCylinderStacked: "stacked", Col3DCylinderPercentStacked: "percentStacked", Line: "standard", Line3D: "standard", } plotAreaChartBarDir = map[ChartType]string{ Bar: "bar", BarStacked: "bar", BarPercentStacked: "bar", Bar3DClustered: "bar", Bar3DStacked: "bar", Bar3DPercentStacked: "bar", Bar3DConeClustered: "bar", Bar3DConeStacked: "bar", Bar3DConePercentStacked: "bar", Bar3DPyramidClustered: "bar", Bar3DPyramidStacked: "bar", Bar3DPyramidPercentStacked: "bar", Bar3DCylinderClustered: "bar", Bar3DCylinderStacked: "bar", Bar3DCylinderPercentStacked: "bar", Col: "col", ColStacked: "col", ColPercentStacked: "col", Col3D: "col", Col3DClustered: "col", Col3DStacked: "col", Col3DPercentStacked: "col", Col3DCone: "col", Col3DConeStacked: "col", Col3DConeClustered: "col", Col3DConePercentStacked: "col", Col3DPyramid: "col", Col3DPyramidClustered: "col", Col3DPyramidStacked: "col", Col3DPyramidPercentStacked: "col", Col3DCylinder: "col", Col3DCylinderClustered: "col", Col3DCylinderStacked: "col", Col3DCylinderPercentStacked: "col", Line: "standard", Line3D: "standard", } barColChartTypes = []ChartType{ Bar, BarStacked, BarPercentStacked, Bar3DClustered, Bar3DStacked, Bar3DPercentStacked, Bar3DConeClustered, Bar3DConeStacked, Bar3DConePercentStacked, Bar3DPyramidClustered, Bar3DPyramidStacked, Bar3DPyramidPercentStacked, Bar3DCylinderClustered, Bar3DCylinderStacked, Bar3DCylinderPercentStacked, Col, ColStacked, ColPercentStacked, Col3D, Col3DClustered, Col3DStacked, Col3DPercentStacked, Col3DCone, Col3DConeStacked, Col3DConeClustered, Col3DConePercentStacked, Col3DPyramid, Col3DPyramidClustered, Col3DPyramidStacked, Col3DPyramidPercentStacked, Col3DCylinder, Col3DCylinderClustered, Col3DCylinderStacked, Col3DCylinderPercentStacked, } orientation = map[bool]string{ true: "maxMin", false: "minMax", } catAxPos = map[bool]string{ true: "t", false: "b", } valAxPos = map[bool]string{ true: "r", false: "l", } tickLblPosVal = map[ChartTickLabelPositionType]string{ ChartTickLabelNextToAxis: "nextTo", ChartTickLabelHigh: "high", ChartTickLabelLow: "low", ChartTickLabelNone: "none", } tickLblPosNone = map[ChartType]string{ Contour: "none", WireframeContour: "none", } ) // parseChartOptions provides a function to parse the format settings of the // chart with default value. func parseChartOptions(opts *Chart) (*Chart, error) { if opts == nil { return nil, ErrParameterInvalid } if opts.Dimension.Width == 0 { opts.Dimension.Width = defaultChartDimensionWidth } if opts.Dimension.Height == 0 { opts.Dimension.Height = defaultChartDimensionHeight } if opts.Legend.Position == "" { opts.Legend.Position = defaultChartLegendPosition } opts.parseTitle() if opts.Fill.Transparency < 0 || 100 < opts.Fill.Transparency { return opts, ErrTransparency } if opts.VaryColors == nil { opts.VaryColors = boolPtr(true) } if opts.Border.Width == 0 { opts.Border.Width = 0.75 } if opts.ShowBlanksAs == "" { opts.ShowBlanksAs = defaultChartShowBlanksAs } format := opts.Format graphicOptions, err := format.parseGraphicOptions(nil) if err != nil { return opts, err } opts.Format = *graphicOptions return opts, opts.parseSeries() } // parseSeries check the series settings of the chart. func (opts *Chart) parseSeries() error { for _, series := range opts.Series { if series.Fill.Transparency < 0 || 100 < series.Fill.Transparency { return ErrTransparency } } return nil } // parseTitle parse the title settings of the chart with default value. func (opts *Chart) parseTitle() { for i := range opts.Title { if opts.Title[i].Font == nil { opts.Title[i].Font = &Font{} } if opts.Title[i].Font.Color == "" { opts.Title[i].Font.Color = "595959" } if opts.Title[i].Font.Size == 0 { opts.Title[i].Font.Size = 14 } } } // AddChart provides the method to add chart in a sheet by given chart format // set (such as offset, scale, aspect ratio setting and print settings) and // properties set. For example, create 3D clustered column chart with data // Sheet1!$E$1:$L$15: // // package main // // import ( // "fmt" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // for idx, row := range [][]interface{}{ // {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, // {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, // } { // cell, err := excelize.CoordinatesToCellName(1, idx+1) // if err != nil { // fmt.Println(err) // return // } // f.SetSheetRow("Sheet1", cell, &row) // } // if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ // Type: excelize.Col3DClustered, // Series: []excelize.ChartSeries{ // { // Name: "Sheet1!$A$2", // Categories: "Sheet1!$B$1:$D$1", // Values: "Sheet1!$B$2:$D$2", // }, // { // Name: "Sheet1!$A$3", // Categories: "Sheet1!$B$1:$D$1", // Values: "Sheet1!$B$3:$D$3", // }, // { // Name: "Sheet1!$A$4", // Categories: "Sheet1!$B$1:$D$1", // Values: "Sheet1!$B$4:$D$4", // }, // }, // Title: []excelize.RichTextRun{ // { // Text: "Fruit 3D Clustered Column Chart", // }, // }, // Legend: excelize.ChartLegend{ // ShowLegendKey: false, // }, // PlotArea: excelize.ChartPlotArea{ // ShowBubbleSize: true, // ShowCatName: false, // ShowLeaderLines: false, // ShowPercent: true, // ShowSerName: true, // ShowVal: true, // }, // }); err != nil { // fmt.Println(err) // return // } // // Save spreadsheet by the given path. // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } // // The following shows the type of chart supported by excelize: // // ID | Enumeration | Chart // ----+-----------------------------+------------------------------ // 0 | Area | 2D area chart // 1 | AreaStacked | 2D stacked area chart // 2 | AreaPercentStacked | 2D 100% stacked area chart // 3 | Area3D | 3D area chart // 4 | Area3DStacked | 3D stacked area chart // 5 | Area3DPercentStacked | 3D 100% stacked area chart // 6 | Bar | 2D clustered bar chart // 7 | BarStacked | 2D stacked bar chart // 8 | BarPercentStacked | 2D 100% stacked bar chart // 9 | Bar3DClustered | 3D clustered bar chart // 10 | Bar3DStacked | 3D stacked bar chart // 11 | Bar3DPercentStacked | 3D 100% stacked bar chart // 12 | Bar3DConeClustered | 3D cone clustered bar chart // 13 | Bar3DConeStacked | 3D cone stacked bar chart // 14 | Bar3DConePercentStacked | 3D cone percent bar chart // 15 | Bar3DPyramidClustered | 3D pyramid clustered bar chart // 16 | Bar3DPyramidStacked | 3D pyramid stacked bar chart // 17 | Bar3DPyramidPercentStacked | 3D pyramid percent stacked bar chart // 18 | Bar3DCylinderClustered | 3D cylinder clustered bar chart // 19 | Bar3DCylinderStacked | 3D cylinder stacked bar chart // 20 | Bar3DCylinderPercentStacked | 3D cylinder percent stacked bar chart // 21 | Col | 2D clustered column chart // 22 | ColStacked | 2D stacked column chart // 23 | ColPercentStacked | 2D 100% stacked column chart // 24 | Col3DClustered | 3D clustered column chart // 25 | Col3D | 3D column chart // 26 | Col3DStacked | 3D stacked column chart // 27 | Col3DPercentStacked | 3D 100% stacked column chart // 28 | Col3DCone | 3D cone column chart // 29 | Col3DConeClustered | 3D cone clustered column chart // 30 | Col3DConeStacked | 3D cone stacked column chart // 31 | Col3DConePercentStacked | 3D cone percent stacked column chart // 32 | Col3DPyramid | 3D pyramid column chart // 33 | Col3DPyramidClustered | 3D pyramid clustered column chart // 34 | Col3DPyramidStacked | 3D pyramid stacked column chart // 35 | Col3DPyramidPercentStacked | 3D pyramid percent stacked column chart // 36 | Col3DCylinder | 3D cylinder column chart // 37 | Col3DCylinderClustered | 3D cylinder clustered column chart // 38 | Col3DCylinderStacked | 3D cylinder stacked column chart // 39 | Col3DCylinderPercentStacked | 3D cylinder percent stacked column chart // 40 | Doughnut | doughnut chart // 41 | Line | line chart // 42 | Line3D | 3D line chart // 43 | Pie | pie chart // 44 | Pie3D | 3D pie chart // 45 | PieOfPie | pie of pie chart // 46 | BarOfPie | bar of pie chart // 47 | Radar | radar chart // 48 | Scatter | scatter chart // 49 | Surface3D | 3D surface chart // 50 | WireframeSurface3D | 3D wireframe surface chart // 51 | Contour | contour chart // 52 | WireframeContour | wireframe contour chart // 53 | Bubble | bubble chart // 54 | Bubble3D | 3D bubble chart // 55 | StockHighLowClose | High-Low-Close stock chart // 56 | StockOpenHighLowClose | Open-High-Low-Close stock chart // // In Excel a chart series is a collection of information that defines which // data is plotted such as values, axis labels and formatting. // // The series options that can be set are: // // Name // Categories // Values // Fill // Legend // Line // Marker // DataLabel // DataLabelPosition // DataPoint // // Name: Set the name for the series. The name is displayed in the chart legend // and in the formula bar. The 'Name' property is optional and if it isn't // supplied it will default to Series 1..n. The name can also be a formula such // as Sheet1!$A$1 // // Categories: This sets the chart category labels. The category is more or less // the same as the X axis. In most chart types the 'Categories' property is // optional and the chart will just assume a sequential series from 1..n. // // Values: This is the most important property of a series and is the only // mandatory option for every chart object. This option links the chart with // the worksheet data that it displays. // // Sizes: This sets the bubble size in a data series. The 'Sizes' property is // optional and the default value was same with 'Values'. // // Fill: This set the format for the data series fill. The 'Fill' property is // optional. // // Legend: This set the font of legend text for a data series. The 'Legend' // property is optional. // // Line: This sets the line format of the line chart. The 'Line' property is // optional and if it isn't supplied it will default style. The options that // can be set are width and color. The range of width is 0.25pt - 999pt. If the // value of width is outside the range, the default width of the line is 2pt. // // Marker: This sets the marker of the line chart and scatter chart. The range // of optional field 'Size' is 2-72 (default value is 5). The enumeration value // of optional field 'Symbol' are (default value is 'auto'): // // circle // dash // diamond // dot // none // picture // plus // square // star // triangle // x // auto // // DataLabel: This sets the format of the chart series data label. // // DataLabelPosition: This sets the position of the chart series data label. // // DataPoint: This sets the format for individual data points in a doughnut, pie // or 3D pie chart series. The 'DataPoint' property is optional. // // Set properties of the chart legend. The options that can be set are: // // Position // ShowLegendKey // Font // // Position: Set the position of the chart legend. The default legend position // is bottom. The available positions are: // // none // top // bottom // left // right // top_right // // ShowLegendKey: Set the legend keys shall be shown in data labels. The default // value is false. // // Font: Set the font properties of the chart legend text. The properties that // can be set are the same as the font object that is used for cell formatting. // The font family, size, color, bold, italic, underline, and strike properties // can be set. // // Set properties of the chart title. The properties that can be set are: // // Title // // Title: Set the name (title) for the chart. The name is displayed above the // chart. The name can also be a formula such as Sheet1!$A$1 or a list with a // sheet name. The name property is optional. The default is to have no chart // title. // // Specifies how blank cells are plotted on the chart by 'ShowBlanksAs'. The // default value is gap. The options that can be set are: // // gap // span // zero // // gap: Specifies that blank values shall be left as a gap. // // span: Specifies that blank values shall be spanned with a line. // // zero: Specifies that blank values shall be treated as zero. // // Specifies that each data marker in the series has a different color by // 'VaryColors'. The default value is true. // // Set chart offset, scale, aspect ratio setting and print settings by 'Format', // same as function 'AddPicture'. // // Set the position of the chart plot area by 'PlotArea'. The properties that // can be set are: // // SecondPlotValues // ShowBubbleSize // ShowCatName // ShowDataTable // ShowDataTableKeys // ShowLeaderLines // ShowPercent // ShowSerName // ShowVal // Fill // UpBars // DownBars // NumFmt // // SecondPlotValues: Specifies the values in second plot for the 'PieOfPie' and // 'BarOfPie' chart. // // ShowBubbleSize: Specifies the bubble size shall be shown in a data label. The // 'ShowBubbleSize' property is optional. The default value is false. // // ShowCatName: Specifies that the category name shall be shown in the data // label. The 'ShowCatName' property is optional. The default value is true. // // ShowDataTable: Used for add data table under chart, depending on the chart // type, only available for area, bar, column and line series type charts. The // 'ShowDataTable' property is optional. The default value is false. // // ShowDataTableKeys: Used for add legend key in data table, only works on // 'ShowDataTable' is enabled. The 'ShowDataTableKeys' property is optional. // The default value is false. // // ShowLeaderLines: Specifies leader lines shall be shown for data labels. The // 'ShowLeaderLines' property is optional. The default value is false. // // ShowPercent: Specifies that the percentage shall be shown in a data label. // The 'ShowPercent' property is optional. The default value is false. // // ShowSerName: Specifies that the series name shall be shown in a data label. // The 'ShowSerName' property is optional. The default value is false. // // ShowVal: Specifies that the value shall be shown in a data label. // The 'ShowVal' property is optional. The default value is false. // // Fill: Set fill color of the chart. // // UpBars: Specifies the format for stock chart up bars. The 'UpBars' property // is optional. // // DownBars: Specifies the format for stock chart down bars. The 'DownBars' // property is optional. // // NumFmt: Specifies that if linked to source and set custom number format code // for data labels. The 'NumFmt' property is optional. The default format code // is 'General'. // // Set the primary horizontal and vertical axis options by 'XAxis' and 'YAxis'. // The properties of 'XAxis' that can be set are: // // None // DropLines // HighLowLines // MajorGridLines // MinorGridLines // TickLabelSkip // ReverseOrder // Maximum // Minimum // Alignment // Font // NumFmt // Title // // The properties of 'YAxis' that can be set are: // // None // MajorGridLines // MinorGridLines // MajorUnit // Secondary // ReverseOrder // Maximum // Minimum // Alignment // Font // LogBase // NumFmt // Title // // None: Disable axes. // // DropLines: Specifies drop lines for the 2D and 3D area and line charts. Drop // lines are vertical lines that connect data points in a chart down to the // horizontal (category) axis. They are often used in Line or Area charts to // make it easier to see the exact category position of each point. The // 'DropLines' property is optional. The default value is false. // // HighLowLines: Specifies high low lines for the 2D line chart. High low lines // displayed by default in stock charts. They extend from the highest value to // the lowest value in each category. The 'HighLowLines' property is optional. // The default value is false. // // MajorGridLines: Specifies major grid lines. // // MinorGridLines: Specifies minor grid lines. // // MajorUnit: Specifies the distance between major ticks. Shall contain a // positive floating-point number. The 'MajorUnit' property is optional. The // default value is auto. // // Secondary: Specifies the current series vertical axis as the secondary axis, // this only works for the second and later chart in the combo chart. The // default value is false. // // TickLabelSkip: Specifies how many tick labels to skip between label that is // drawn. The 'TickLabelSkip' property is optional. The default value is auto. // // ReverseOrder: Specifies that the categories or values on reverse order // (orientation of the chart). The 'ReverseOrder' property is optional. The // default value is false. // // Maximum: Specifies that the fixed maximum, 0 is auto. The 'Maximum' property // is optional. The default value is auto. // // Minimum: Specifies that the fixed minimum, 0 is auto. The 'Minimum' property // is optional. The default value is auto. // // Alignment: Specifies that the alignment of the horizontal and vertical axis. // The properties of alignment that can be set are: // // TextRotation // Vertical // // The value of 'TextRotation' that can be set from -90 to 90. // // The value of 'Vertical' that can be set are: // // horz // vert // vert270 // wordArtVert // eaVert // mongolianVert // wordArtVertRtl // // Font: Specifies that the font of the horizontal and vertical axis. The // properties of font that can be set are: // // Bold // Italic // Underline // Family // Size // Strike // Color // VertAlign // // LogBase: Specifies logarithmic scale base number of the vertical axis. // // NumFmt: Specifies that if linked to source and set custom number format code // for axis. The 'NumFmt' property is optional. The default format code is // 'General'. // // Title: Specifies that the primary horizontal or vertical axis title and // resize chart. The 'Title' property is optional. // // Set chart size by 'Dimension' property. The 'Dimension' property is optional. // The default width is 480, and height is 260. // // Set chart legend for all data series by 'Legend' property. The 'Legend' // property is optional. // // Set the bubble size in all data series for the bubble chart or 3D bubble // chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional. The // default width is 100, and the value should be great than 0 and less or equal // than 300. // // Set the doughnut hole size in all data series for the doughnut chart by // 'HoleSize' property. The 'HoleSize' property is optional. The default width // is 75, and the value should be great than 0 and less or equal than 90. // // Set the gap with of the column and bar series chart by 'GapWidth' property. // The 'GapWidth' property is optional. The default width is 150, and the value // should be great or equal than 0 and less or equal than 500. // // Set series overlap of the column and bar series chart by 'Overlap' property. // The 'Overlap' property is optional. The default width is 0, and the value // should be great or equal than -100 and less or equal than 100. // // combo: Specifies the create a chart that combines two or more chart types in // a single chart. For example, create a clustered column - line chart with // data Sheet1!$E$1:$L$15: // // package main // // import ( // "fmt" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // for idx, row := range [][]interface{}{ // {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, // {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, // } { // cell, err := excelize.CoordinatesToCellName(1, idx+1) // if err != nil { // fmt.Println(err) // return // } // f.SetSheetRow("Sheet1", cell, &row) // } // enable, disable := true, false // if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ // Type: excelize.Col, // Series: []excelize.ChartSeries{ // { // Name: "Sheet1!$A$2", // Categories: "Sheet1!$B$1:$D$1", // Values: "Sheet1!$B$2:$D$2", // }, // }, // Format: excelize.GraphicOptions{ // ScaleX: 1, // ScaleY: 1, // OffsetX: 15, // OffsetY: 10, // PrintObject: &enable, // LockAspectRatio: false, // Locked: &disable, // }, // Title: []excelize.RichTextRun{ // { // Text: "Clustered Column - Line Chart", // }, // }, // Legend: excelize.ChartLegend{ // Position: "left", // ShowLegendKey: false, // }, // PlotArea: excelize.ChartPlotArea{ // ShowCatName: false, // ShowLeaderLines: false, // ShowPercent: true, // ShowSerName: true, // ShowVal: true, // }, // }, &excelize.Chart{ // Type: excelize.Line, // Series: []excelize.ChartSeries{ // { // Name: "Sheet1!$A$4", // Categories: "Sheet1!$B$1:$D$1", // Values: "Sheet1!$B$4:$D$4", // Marker: excelize.ChartMarker{ // Symbol: "none", Size: 10, // }, // }, // }, // Format: excelize.GraphicOptions{ // ScaleX: 1, // ScaleY: 1, // OffsetX: 15, // OffsetY: 10, // PrintObject: &enable, // LockAspectRatio: false, // Locked: &disable, // }, // Legend: excelize.ChartLegend{ // Position: "right", // ShowLegendKey: false, // }, // PlotArea: excelize.ChartPlotArea{ // ShowCatName: false, // ShowLeaderLines: false, // ShowPercent: true, // ShowSerName: true, // ShowVal: true, // }, // }); err != nil { // fmt.Println(err) // return // } // // Save spreadsheet by the given path. // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } func (f *File) AddChart(sheet, cell string, chart *Chart, combo ...*Chart) error { // Read worksheet data ws, err := f.workSheetReader(sheet) if err != nil { return err } opts, comboCharts, err := f.getChartOptions(chart, combo) if err != nil { return err } // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. drawingID := f.countDrawings() + 1 chartID := f.countCharts() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") err = f.addDrawingChart(sheet, drawingXML, cell, int(opts.Dimension.Width), int(opts.Dimension.Height), drawingRID, &opts.Format) if err != nil { return err } f.addChart(opts, comboCharts) if err = f.addContentTypePart(chartID, "chart"); err != nil { return err } _ = f.addContentTypePart(drawingID, "drawings") f.addSheetNameSpace(sheet, SourceRelationship) return err } // AddChartSheet provides the method to create a chartsheet by given chart // format set (such as offset, scale, aspect ratio setting and print settings) // and properties set. In Excel a chartsheet is a worksheet that only contains // a chart. func (f *File) AddChartSheet(sheet string, chart *Chart, combo ...*Chart) error { // Check if the worksheet already exists idx, err := f.GetSheetIndex(sheet) if err != nil { return err } if idx != -1 { return ErrExistsSheet } opts, comboCharts, err := f.getChartOptions(chart, combo) if err != nil { return err } cs := xlsxChartsheet{ SheetViews: &xlsxChartsheetViews{ SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}}, }, } f.SheetCount++ wb, _ := f.workbookReader() sheetID := 0 for _, v := range wb.Sheets.Sheet { if v.SheetID > sheetID { sheetID = v.SheetID } } sheetID++ path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml" f.sheetMap[sheet] = path f.Sheet.Store(path, nil) drawingID := f.countDrawings() + 1 chartID := f.countCharts() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" f.prepareChartSheetDrawing(&cs, drawingID, sheet) drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") if err = f.addSheetDrawingChart(drawingXML, drawingRID, &opts.Format); err != nil { return err } f.addChart(opts, comboCharts) if err = f.addContentTypePart(chartID, "chart"); err != nil { return err } _ = f.addContentTypePart(sheetID, "chartsheet") _ = f.addContentTypePart(drawingID, "drawings") // Update workbook.xml.rels rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") // Update workbook.xml f.setWorkbook(sheet, sheetID, rID) chartsheet, _ := xml.Marshal(cs) f.addSheetNameSpace(sheet, NameSpaceSpreadSheet) f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet))) return err } // getChartOptions provides a function to check format set of the chart and // create chart format. func (f *File) getChartOptions(opts *Chart, combo []*Chart) (*Chart, []*Chart, error) { var comboCharts []*Chart options, err := parseChartOptions(opts) if err != nil { return options, comboCharts, err } for _, comboFormat := range combo { comboChart, err := parseChartOptions(comboFormat) if err != nil { return options, comboCharts, err } if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok { return options, comboCharts, newUnsupportedChartType(comboChart.Type) } comboCharts = append(comboCharts, comboChart) } if _, ok := chartValAxNumFmtFormatCode[options.Type]; !ok { return options, comboCharts, newUnsupportedChartType(options.Type) } return options, comboCharts, err } // DeleteChart provides a function to delete chart in spreadsheet by given // worksheet name and cell reference. func (f *File) DeleteChart(sheet, cell string) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } col-- row-- ws, err := f.workSheetReader(sheet) if err != nil { return err } if ws.Drawing == nil { return err } drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl") _, err = f.deleteDrawing(col, row, drawingXML, "Chart") return err } // countCharts provides a function to get chart files count storage in the // folder xl/charts. func (f *File) countCharts() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/charts/chart") { count++ } return true }) return count } // ptToEMUs provides a function to convert pt to EMUs, 1 pt = 12700 EMUs. The // range of pt is 0.25pt - 999pt. If the value of pt is outside the range, the // default EMUs will be returned. func (f *File) ptToEMUs(pt float64) int { if 0.25 > pt || pt > 999 { return 25400 } return int(12700 * pt) } ================================================ FILE: chart_test.go ================================================ package excelize import ( "bytes" "encoding/xml" "fmt" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestChartSize(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) categories := map[string]string{ "A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear", } for cell, v := range categories { assert.NoError(t, f.SetCellValue(sheet1, cell, v)) } values := map[string]int{ "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8, } for cell, v := range values { assert.NoError(t, f.SetCellValue(sheet1, cell, v)) } assert.NoError(t, f.AddChart("Sheet1", "E4", &Chart{ Type: Col3DClustered, Dimension: ChartDimension{ Width: 640, Height: 480, }, Series: []ChartSeries{ {Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"}, {Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"}, {Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"}, }, Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, })) var buffer bytes.Buffer // Save spreadsheet by the given path. assert.NoError(t, f.Write(&buffer)) newFile, err := OpenReader(&buffer) assert.NoError(t, err) chartsNum := newFile.countCharts() if !assert.Equal(t, 1, chartsNum, "Expected 1 chart, actual %d", chartsNum) { t.FailNow() } var ( workdir decodeWsDr anchor decodeCellAnchor ) content, ok := newFile.Pkg.Load("xl/drawings/drawing1.xml") assert.True(t, ok, "Can't open the chart") err = xml.Unmarshal(content.([]byte), &workdir) if !assert.NoError(t, err) { t.FailNow() } err = xml.Unmarshal([]byte(""+ workdir.TwoCellAnchor[0].Content+""), &anchor) if !assert.NoError(t, err) { t.FailNow() } if !assert.Equal(t, 4, anchor.From.Col, "Expected 'from' column 4") || !assert.Equal(t, 3, anchor.From.Row, "Expected 'from' row 3") { t.FailNow() } assert.Equal(t, 14, anchor.To.Col, "Expected 'to' column 14") assert.Equal(t, 29, anchor.To.Row, "Expected 'to' row 29") } func TestAddDrawingChart(t *testing.T) { f := NewFile() assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)}), "XML syntax error on line 1: invalid UTF-8") } func TestAddSheetDrawingChart(t *testing.T) { f := NewFile() path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) assert.EqualError(t, f.addSheetDrawingChart(path, 0, &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteDrawing(t *testing.T) { f := NewFile() path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) _, err := f.deleteDrawing(0, 0, path, "Chart") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) f.Drawings.Store(path, &xlsxWsDr{OneCellAnchor: []*xdrCellAnchor{{ GraphicFrame: string(MacintoshCyrillicCharset), }}}) _, err = f.deleteDrawing(0, 0, path, "Chart") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f.Drawings.Store(path, &xlsxWsDr{TwoCellAnchor: []*xdrCellAnchor{{ GraphicFrame: string(MacintoshCyrillicCharset), }}}) _, err = f.deleteDrawing(0, 0, path, "Chart") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestAddChart(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } categories := map[string]string{"A30": "SS", "A31": "S", "A32": "M", "A33": "L", "A34": "LL", "A35": "XL", "A36": "XXL", "A37": "XXXL", "B29": "Apple", "C29": "Orange", "D29": "Pear"} values := map[string]int{"B30": 1, "C30": 1, "D30": 1, "B31": 2, "C31": 2, "D31": 2, "B32": 3, "C32": 3, "D32": 3, "B33": 4, "C33": 4, "D33": 4, "B34": 5, "C34": 5, "D34": 5, "B35": 6, "C35": 6, "D35": 6, "B36": 7, "C36": 7, "D36": 7, "B37": 8, "C37": 8, "D37": 8} for k, v := range categories { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } for k, v := range values { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } assert.EqualError(t, f.AddChart("Sheet1", "P1", nil), ErrParameterInvalid.Error()) // Test add chart on not exists worksheet assert.EqualError(t, f.AddChart("SheetN", "P1", nil), "sheet SheetN does not exist") // Test add chart with invalid positioning types assert.Equal(t, f.AddChart("Sheet1", "P1", &Chart{ Format: GraphicOptions{Positioning: "x"}, }), newInvalidOptionalValue("Positioning", "x", supportedPositioning)) maximum, minimum, zero := 7.5, 0.5, .0 series := []ChartSeries{ {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}, {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, { Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Marker: ChartMarker{ Fill: Fill{Type: "pattern", Color: []string{"FFFFFF"}, Pattern: 1}, Border: ChartLine{ Type: ChartLineSolid, Dash: ChartDashDot, Fill: Fill{Type: "pattern", Color: []string{"#FFFF00"}, Pattern: 1}, Width: 1, }, }, Legend: ChartLegend{Font: &Font{Family: "Arial", Size: 11, Strike: true, Color: "777777"}}, }, } series2 := []ChartSeries{ { Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Fill: Fill{Type: "pattern", Color: []string{"000000"}, Pattern: 1, Transparency: 60}, Marker: ChartMarker{Symbol: "none", Size: 10}, }, {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Line: ChartLine{Dash: ChartDashDash, Width: 0.25}}, } series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}} series4 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37", DataPoint: []ChartDataPoint{ {Index: 0, Fill: Fill{Type: "pattern", Color: []string{"003F5C"}, Pattern: 1}}, {Index: 1, Fill: Fill{Type: "pattern", Color: []string{"58508D"}, Pattern: 1}}, {Index: 2, Fill: Fill{Type: "pattern", Color: []string{"BC5090"}, Pattern: 1}}, {Index: 3, Fill: Fill{Type: "pattern", Color: []string{"FF6361"}, Pattern: 1}}, }}} series5 := []ChartSeries{ {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Sizes: "Sheet1!$B$30:$D$30", DataLabelPosition: ChartDataLabelsPositionAbove}, {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31", Sizes: "Sheet1!$B$31:$D$31", DataLabelPosition: ChartDataLabelsPositionLeft}, {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32", Sizes: "Sheet1!$B$32:$D$32", DataLabelPosition: ChartDataLabelsPositionBestFit}, {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33", Sizes: "Sheet1!$B$33:$D$33", DataLabelPosition: ChartDataLabelsPositionCenter}, {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34", Sizes: "Sheet1!$B$34:$D$34", DataLabelPosition: ChartDataLabelsPositionInsideBase}, {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35", Sizes: "Sheet1!$B$35:$D$35", DataLabelPosition: ChartDataLabelsPositionInsideEnd}, {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36", Sizes: "Sheet1!$B$36:$D$36", DataLabelPosition: ChartDataLabelsPositionOutsideEnd}, {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Sizes: "Sheet1!$B$37:$D$37", DataLabelPosition: ChartDataLabelsPositionRight}, } format := GraphicOptions{ ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale, OffsetX: 15, OffsetY: 10, PrintObject: boolPtr(true), LockAspectRatio: false, Locked: boolPtr(false), } legend := ChartLegend{Position: "left", ShowLegendKey: false} plotArea := ChartPlotArea{ SecondPlotValues: 3, ShowBubbleSize: true, ShowCatName: true, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true, Fill: Fill{Type: "pattern", Pattern: 1}, } for _, c := range []struct { sheetName, cell string opts *Chart }{ {sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: []RichTextRun{{Text: "2D Column Chart", Font: &Font{Size: 11, Family: "Calibri"}}}, PlotArea: plotArea, Border: ChartLine{Type: ChartLineNone}, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Family: "Times New Roman", Size: 15, Strike: true, Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}}, {sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: ColStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Pattern: 1}, Border: ChartLine{Type: ChartLineAutomatic}, ShowBlanksAs: "zero", GapWidth: uintPtr(10), Overlap: intPtr(100)}}, {sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: ColPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "100% Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Color: []string{"EEEEEE"}, Pattern: 1}, Border: ChartLine{Type: ChartLineSolid, Width: 2}, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "wordArtVertRtl", TextRotation: 0}}}}, {sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: Col3DClustered, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false, Font: &Font{Size: 10}}, Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: Col3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert", TextRotation: 0}}}}, {sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: Col3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: Radar, Series: series, Format: format, Legend: ChartLegend{Position: "top_right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Radar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "span"}}, {sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: Col3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AF16", opts: &Chart{Type: Col3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AF30", opts: &Chart{Type: Col3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AF45", opts: &Chart{Type: Col3DCone, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AN1", opts: &Chart{Type: Col3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AN16", opts: &Chart{Type: Col3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AN30", opts: &Chart{Type: Col3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AN45", opts: &Chart{Type: Col3DPyramid, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AV1", opts: &Chart{Type: Col3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: Col3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: Col3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "AV45", opts: &Chart{Type: Col3DCylinder, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: Col3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert270", TextRotation: 0}}}}, {sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: Line3D, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true, MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}}}, {sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: Scatter, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Scatter Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: Doughnut, Series: series3, Format: format, Legend: ChartLegend{Position: "right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Doughnut Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: false, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "zero", HoleSize: 30}}, {sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: Line, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true, HighLowLines: true, MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, TickLabelPosition: ChartTickLabelLow}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}}, {sheetName: "Sheet2", cell: "P32", opts: &Chart{Type: Pie3D, Series: series3, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: Pie, Series: series4, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Pie Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false, NumFmt: ChartNumFmt{CustomNumFmt: "0.00%;0;;"}}, ShowBlanksAs: "gap"}}, // bar series chart {sheetName: "Sheet2", cell: "P48", opts: &Chart{Type: Bar, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "X48", opts: &Chart{Type: BarStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: BarPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked 100% Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: Bar3DClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: Bar3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{Maximum: &maximum, Minimum: &minimum}}}, {sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: Bar3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Secondary: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}}, // area series chart {sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: Area, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, {sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: AreaStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, {sheetName: "Sheet2", cell: "AF16", opts: &Chart{Type: AreaPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, {sheetName: "Sheet2", cell: "AN16", opts: &Chart{Type: Area3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, {sheetName: "Sheet2", cell: "AF32", opts: &Chart{Type: Area3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, {sheetName: "Sheet2", cell: "AN32", opts: &Chart{Type: Area3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}}, // cylinder series chart {sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: Bar3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: Bar3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: Bar3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // cone series chart {sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: Bar3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: Bar3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: Bar3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: Bar3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: Bar3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: Bar3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // surface series chart {sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: Surface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, {sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: WireframeSurface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Wireframe Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: Contour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: WireframeContour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Wireframe Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // bubble chart {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series5, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", BubbleSize: 75}}, {sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: Bubble3D, Series: series5, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, // pie of pie chart {sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: PieOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Pie of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, // bar of pie chart {sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: BarOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, } { assert.NoError(t, f.AddChart(c.sheetName, c.cell, c.opts)) } // combo chart _, err = f.NewSheet("Combo Charts") assert.NoError(t, err) clusteredColumnCombo := [][]interface{}{ {"A1", Line, "Clustered Column - Line Chart"}, {"I1", Doughnut, "Clustered Column - Doughnut Chart"}, } for _, props := range clusteredColumnCombo { assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[2].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowDataTable: true, ShowDataTableKeys: true, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}, YAxis: ChartAxis{Secondary: true}})) } stackedAreaCombo := map[string][]interface{}{ "A16": {Line, "Stacked Area - Line Chart"}, "I16": {Doughnut, "Stacked Area - Doughnut Chart"}, } for axis, props := range stackedAreaCombo { assert.NoError(t, f.AddChart("Combo Charts", axis, &Chart{Type: AreaStacked, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[1].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}})) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx"))) // Test with invalid sheet name assert.EqualError(t, f.AddChart("Sheet:1", "A1", &Chart{Type: Col, Series: series[:1]}), ErrSheetNameInvalid.Error()) // Test with illegal cell reference assert.EqualError(t, f.AddChart("Sheet2", "A", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test with unsupported chart type assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: 0x39, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newUnsupportedChartType(0x39).Error()) // Test add combo chart with invalid format set assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}, nil), ErrParameterInvalid.Error()) // Test add combo chart with unsupported chart type assert.EqualError(t, f.AddChart("Sheet2", "BD64", &Chart{Type: BarOfPie, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}, &Chart{Type: 0x39, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}), newUnsupportedChartType(0x39).Error()) // Test add chart with series transparency value exceeds limit assert.Equal(t, ErrTransparency, f.AddChart("Sheet1", "BD64", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Fill: Fill{Transparency: 110}}}})) // Test add chart with transparency value exceeds limit assert.Equal(t, ErrTransparency, f.AddChart("Sheet1", "BD64", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Fill: Fill{Transparency: -1}})) assert.NoError(t, f.Close()) // Test add chart with unsupported charset content types. f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddChart("Sheet1", "P1", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8") t.Run("for_create_stock_chart", func(t *testing.T) { f := NewFile() for i, row := range [][]interface{}{ {"Date", "Volume", "Open", "High", "Low", "Close"}, {45593, 14864000, 431.66, 431.94, 426.3, 426.59}, {45590, 16899100, 426.76, 432.52, 426.57, 428.15}, {45589, 13581600, 425.33, 425.98, 422.4, 424.73}, {45588, 19654400, 430.86, 431.08, 422.53, 424.6}, {45587, 25482200, 418.49, 430.58, 418.04, 427.51}, {45586, 14206100, 416.12, 418.96, 413.75, 418.78}, {45583, 17145300, 417.14, 419.65, 416.26, 418.16}, {45582, 14820000, 422.36, 422.5, 415.59, 416.72}, {45581, 15508900, 415.17, 416.36, 410.48, 416.12}, {45580, 18900200, 422.18, 422.48, 415.26, 418.74}, {45579, 16653100, 417.77, 424.04, 417.52, 419.14}, {45576, 14144900, 416.14, 417.13, 413.25, 416.32}, {45575, 13848400, 415.23, 417.35, 413.15, 415.84}, {45574, 14974300, 415.86, 420.38, 414.3, 417.46}, {45573, 19229300, 410.9, 415.66, 408.17, 414.71}, {45572, 20919800, 416, 417.11, 409, 409.54}, {45569, 19169700, 418.24, 419.75, 414.97, 416.06}, {45568, 13686400, 417.63, 419.55, 414.29, 416.54}, {45567, 16582300, 422.58, 422.82, 416.71, 417.13}, {45566, 19092900, 428.45, 428.48, 418.81, 420.69}, {45565, 16807300, 428.21, 430.42, 425.37, 430.3}, } { cell, err := CoordinatesToCellName(1, i+1) assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row)) } style, err := f.NewStyle(&Style{NumFmt: 15}) assert.NoError(t, err) assert.NoError(t, f.SetColStyle("Sheet1", "A", style)) assert.NoError(t, f.AddChart("Sheet1", "G1", &Chart{ Type: StockHighLowClose, Series: []ChartSeries{ {Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"}, {Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"}, {Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"}, }, Legend: ChartLegend{Position: "none"}, Title: []RichTextRun{{Text: "High-Low-Close Stock Chart"}}, XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}}, })) assert.NoError(t, f.AddChart("Sheet1", "G16", &Chart{ Type: StockOpenHighLowClose, Series: []ChartSeries{ {Name: "Sheet1!$C$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$C$2:$C$22"}, {Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"}, {Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"}, {Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"}, }, Legend: ChartLegend{Position: "none"}, Title: []RichTextRun{{Text: "Open-High-Low-Close Stock Chart"}}, XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}}, PlotArea: ChartPlotArea{ UpBars: ChartUpDownBar{ Border: ChartLine{Type: ChartLineNone}, Fill: Fill{Type: "pattern", Color: []string{"00B050"}, Pattern: 1}, }, DownBars: ChartUpDownBar{ Border: ChartLine{Type: ChartLineNone}, Fill: Fill{Type: "pattern", Color: []string{"FF0000"}, Pattern: 1}, }, }, })) assert.NoError(t, f.AddChart("Sheet1", "O1", &Chart{ Type: Col, Series: []ChartSeries{ {Name: "Sheet1!$B$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$B$2:$B$22"}, }, VaryColors: boolPtr(false), XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}}, YAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "#,##0"}}, Title: []RichTextRun{{Text: "Volume-High-Low-Close Stock Chart"}}, }, &Chart{ Type: StockHighLowClose, Series: []ChartSeries{ {Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"}, {Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"}, {Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"}, }, YAxis: ChartAxis{Secondary: true}, })) assert.NoError(t, f.AddChart("Sheet1", "O16", &Chart{ Type: Col, Series: []ChartSeries{ {Name: "Sheet1!$B$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$B$2:$B$22"}, }, VaryColors: boolPtr(false), XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}}, YAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "#,##0"}}, Title: []RichTextRun{{Text: "Volume-Open-High-Low-Close Stock Chart"}}, }, &Chart{ Type: StockOpenHighLowClose, Series: []ChartSeries{ {Name: "Sheet1!$C$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$C$2:$C$22"}, {Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"}, {Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"}, {Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"}, }, YAxis: ChartAxis{Secondary: true}, })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartStock.xlsx"))) }) } func TestAddChartSheet(t *testing.T) { categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"} values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} f := NewFile() for k, v := range categories { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } for k, v := range values { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } series := []ChartSeries{ {Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"}, {Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"}, {Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"}, } assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}})) // Test set the chartsheet as active sheet var sheetIdx int for idx, sheetName := range f.GetSheetList() { if sheetName != "Chart1" { continue } sheetIdx = idx } f.SetActiveSheet(sheetIdx) // Test cell value on chartsheet assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet") // Test add chartsheet on already existing name sheet assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrExistsSheet.Error()) // Test add chartsheet with invalid sheet name assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrSheetNameInvalid.Error()) // Test with unsupported chart type assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: 0x39, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), newUnsupportedChartType(0x39).Error()) assert.NoError(t, f.UpdateLinkedValue()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx"))) // Test add chart sheet with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddChartSheet("Chart4", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteChart(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.DeleteChart("Sheet1", "A1")) series := []ChartSeries{ {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}, {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"}, } format := GraphicOptions{ Name: "Chart 3", AltText: "chart", ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale, OffsetX: 15, OffsetY: 10, PrintObject: boolPtr(true), LockAspectRatio: false, Locked: boolPtr(false), } legend := ChartLegend{Position: "left", ShowLegendKey: false} plotArea := ChartPlotArea{ ShowBubbleSize: true, ShowCatName: true, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true, } assert.NoError(t, f.AddChart("Sheet1", "P1", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"})) assert.NoError(t, f.DeleteChart("Sheet1", "P1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx"))) // Test delete chart with invalid sheet name assert.EqualError(t, f.DeleteChart("Sheet:1", "P1"), ErrSheetNameInvalid.Error()) // Test delete chart on not exists worksheet assert.EqualError(t, f.DeleteChart("SheetN", "A1"), "sheet SheetN does not exist") // Test delete chart with invalid coordinates assert.EqualError(t, f.DeleteChart("Sheet1", ""), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) // Test delete chart on no chart worksheet assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1")) assert.NoError(t, f.Close()) } func TestChartWithLogarithmicBase(t *testing.T) { // Create test workbook with data f := NewFile() sheet1 := f.GetSheetName(0) categories := map[string]float64{ "A1": 1, "A2": 2, "A3": 3, "A4": 4, "A5": 5, "A6": 6, "A7": 7, "A8": 8, "A9": 9, "A10": 10, "B1": 0.1, "B2": 1, "B3": 2, "B4": 3, "B5": 20, "B6": 30, "B7": 100, "B8": 500, "B9": 700, "B10": 5000, } for cell, v := range categories { assert.NoError(t, f.SetCellValue(sheet1, cell, v)) } series := []ChartSeries{{Name: "value", Categories: "Sheet1!$A$1:$A$19", Values: "Sheet1!$B$1:$B$10"}} dimension := []uint{640, 480, 320, 240} for _, c := range []struct { cell string opts *Chart }{ {cell: "C1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "Line chart without log scaling"}}}}, {cell: "M1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 10.5 scaling"}}, YAxis: ChartAxis{LogBase: 10.5}}}, {cell: "A25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1.9 scaling"}}, YAxis: ChartAxis{LogBase: 1.9}}}, {cell: "F25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 2 scaling"}}, YAxis: ChartAxis{LogBase: 2}}}, {cell: "K25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1000.1 scaling"}}, YAxis: ChartAxis{LogBase: 1000.1}}}, {cell: "P25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1000 scaling"}}, YAxis: ChartAxis{LogBase: 1000}}}, } { // Add two chart, one without and one with log scaling assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts)) } // Export workbook for human confirmation assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx"))) // Write the workbook to a buffer var buffer bytes.Buffer assert.NoError(t, f.Write(&buffer)) // Read back the workbook from the buffer newFile, err := OpenReader(&buffer) assert.NoError(t, err) // Check the number of charts expectedChartsCount := 6 chartsNum := newFile.countCharts() if !assert.Equal(t, expectedChartsCount, chartsNum, "Expected %d charts, actual %d", expectedChartsCount, chartsNum) { t.FailNow() } chartSpaces := make([]xlsxChartSpace, expectedChartsCount) type xmlChartContent []byte xmlCharts := make([]xmlChartContent, expectedChartsCount) expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000} var ( drawingML interface{} ok bool ) for i := 0; i < expectedChartsCount; i++ { chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1) if drawingML, ok = newFile.Pkg.Load(chartPath); ok { xmlCharts[i] = drawingML.([]byte) } assert.True(t, ok, "Can't open the %s", chartPath) err = xml.Unmarshal(xmlCharts[i], &chartSpaces[i]) if !assert.NoError(t, err) { t.FailNow() } chartLogBasePtr := chartSpaces[i].Chart.PlotArea.ValAx[0].Scaling.LogBase if expectedChartsLogBase[i] == 0 { if !assert.Nil(t, chartLogBasePtr, "LogBase is not nil") { t.FailNow() } } else { if !assert.NotNil(t, chartLogBasePtr, "LogBase is nil") { t.FailNow() } if !assert.Equal(t, expectedChartsLogBase[i], *(chartLogBasePtr.Val), "Expected log base to %f, actual %f", expectedChartsLogBase[i], *(chartLogBasePtr.Val)) { t.FailNow() } } } } ================================================ FILE: col.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "strconv" "strings" "unicode" "github.com/tiendc/go-deepcopy" "golang.org/x/text/width" ) // Cols defines an iterator to a sheet type Cols struct { err error curCol, totalCols, totalRows, stashCol int rawCellValue bool sheet string f *File sheetXML []byte sst *xlsxSST } // GetCols gets the value of all cells by columns on the worksheet based on the // given worksheet name, returned as a two-dimensional array, where the value // of the cell is converted to the `string` type. If the cell format can be // applied to the value of the cell, the applied value will be used, otherwise // the original value will be used. // // For example, get and traverse the value of all cells by columns on a // worksheet named // 'Sheet1': // // cols, err := f.GetCols("Sheet1") // if err != nil { // fmt.Println(err) // return // } // for _, col := range cols { // for _, rowCell := range col { // fmt.Print(rowCell, "\t") // } // fmt.Println() // } func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) { cols, err := f.Cols(sheet) if err != nil { return nil, err } results := make([][]string, 0, 64) for cols.Next() { col, _ := cols.Rows(opts...) results = append(results, col) } return results, nil } // Next will return true if the next column is found. func (cols *Cols) Next() bool { cols.curCol++ return cols.curCol <= cols.totalCols } // Error will return an error when the error occurs. func (cols *Cols) Error() error { return cols.err } // Rows return the current column's row values. func (cols *Cols) Rows(opts ...Options) ([]string, error) { var rowIterator rowXMLIterator if cols.stashCol >= cols.curCol { return rowIterator.cells, rowIterator.err } cols.rawCellValue = cols.f.getOptions(opts...).RawCellValue if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowIterator.cellCol = 0 rowIterator.cellRow++ attrR, _ := attrValToInt("r", xmlElement.Attr) if attrR != 0 { rowIterator.cellRow = attrR } } if cols.rowXMLHandler(&rowIterator, &xmlElement, decoder); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return rowIterator.cells, rowIterator.err } } } return rowIterator.cells, rowIterator.err } // columnXMLIterator defined runtime use field for the worksheet column SAX parser. type columnXMLIterator struct { err error cols Cols cellCol, curRow, row int } // columnXMLHandler parse the column XML element of the worksheet. func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartElement) { colIterator.err = nil inElement := xmlElement.Name.Local if inElement == "row" { colIterator.row++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil { return } colIterator.row = colIterator.curRow } } colIterator.cols.totalRows = colIterator.row colIterator.cellCol = 0 } if inElement == "c" { colIterator.cellCol++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil { return } } } if colIterator.cellCol > colIterator.cols.totalCols { colIterator.cols.totalCols = colIterator.cellCol } } } // rowXMLHandler parse the row XML element of the worksheet. func (cols *Cols) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, decoder *xml.Decoder) { if rowIterator.inElement == "c" { rowIterator.cellCol++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil { return } } } blank := rowIterator.cellRow - len(rowIterator.cells) for i := 1; i < blank; i++ { rowIterator.cells = append(rowIterator.cells, "") } if rowIterator.cellCol == cols.curCol { colCell := xlsxC{} _ = decoder.DecodeElement(&colCell, xmlElement) val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue) rowIterator.cells = append(rowIterator.cells, val) } } } // Cols returns a columns iterator, used for streaming reading data for a // worksheet with a large data. This function is concurrency safe. For // example: // // cols, err := f.Cols("Sheet1") // if err != nil { // fmt.Println(err) // return // } // for cols.Next() { // col, err := cols.Rows() // if err != nil { // fmt.Println(err) // } // for _, rowCell := range col { // fmt.Print(rowCell, "\t") // } // fmt.Println() // } func (f *File) Cols(sheet string) (*Cols, error) { if err := checkSheetName(sheet); err != nil { return nil, err } name, ok := f.getSheetXMLPath(sheet) if !ok { return nil, ErrSheetNotExist{sheet} } if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { ws := worksheet.(*xlsxWorksheet) ws.mu.Lock() defer ws.mu.Unlock() output, _ := xml.Marshal(ws) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var colIterator columnXMLIterator colIterator.cols.sheetXML = f.readBytes(name) decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML)) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: columnXMLHandler(&colIterator, &xmlElement) if colIterator.err != nil { return &colIterator.cols, colIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { colIterator.cols.f = f colIterator.cols.sheet = sheet return &colIterator.cols, nil } } } return &colIterator.cols, nil } // GetColVisible provides a function to get visible of a single column by given // worksheet name and column name. This function is concurrency safe. For // example, get visible state of column D in Sheet1: // // visible, err := f.GetColVisible("Sheet1", "D") func (f *File) GetColVisible(sheet, col string) (bool, error) { colNum, err := ColumnNameToNumber(col) if err != nil { return true, err } f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return false, err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() if ws.Cols == nil { return true, err } visible := true for c := range ws.Cols.Col { colData := &ws.Cols.Col[c] if colData.Min <= colNum && colNum <= colData.Max { visible = !colData.Hidden } } return visible, err } // SetColVisible provides a function to set visible columns by given worksheet // name, columns range and visibility. This function is concurrency safe. // // For example hide column D on Sheet1: // // err := f.SetColVisible("Sheet1", "D", false) // // Hide the columns from D to F (included): // // err := f.SetColVisible("Sheet1", "D:F", false) func (f *File) SetColVisible(sheet, columns string, visible bool) error { minVal, maxVal, err := f.parseColRange(columns) if err != nil { return err } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.mu.Lock() defer ws.mu.Unlock() ws.setColVisible(minVal, maxVal, visible) return err } // setColVisible provides a function to set the visibility of a single column // or multiple columns by given column number. func (ws *xlsxWorksheet) setColVisible(minVal, maxVal int, visible bool) { colData := xlsxCol{ Min: minVal, Max: maxVal, Width: float64Ptr(defaultColWidth), Hidden: !visible, CustomWidth: true, } if ws.Cols == nil { cols := xlsxCols{} cols.Col = append(cols.Col, colData) ws.Cols = &cols return } ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { fc.BestFit = c.BestFit fc.Collapsed = c.Collapsed fc.CustomWidth = c.CustomWidth fc.OutlineLevel = c.OutlineLevel fc.Phonetic = c.Phonetic fc.Style = c.Style fc.Width = c.Width return fc }) } // GetColOutlineLevel provides a function to get outline level of a single // column by given worksheet name and column name. For example, get outline // level of column D in Sheet1: // // level, err := f.GetColOutlineLevel("Sheet1", "D") func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) { level := uint8(0) colNum, err := ColumnNameToNumber(col) if err != nil { return level, err } ws, err := f.workSheetReader(sheet) if err != nil { return 0, err } if ws.Cols == nil { return level, err } for c := range ws.Cols.Col { colData := &ws.Cols.Col[c] if colData.Min <= colNum && colNum <= colData.Max { level = colData.OutlineLevel } } return level, err } // parseColRange parse and convert column range with column name to the column number. func (f *File) parseColRange(columns string) (minVal, maxVal int, err error) { colsTab := strings.Split(columns, ":") minVal, err = ColumnNameToNumber(colsTab[0]) if err != nil { return } maxVal = minVal if len(colsTab) == 2 { if maxVal, err = ColumnNameToNumber(colsTab[1]); err != nil { return } } if maxVal < minVal { minVal, maxVal = maxVal, minVal } return } // SetColOutlineLevel provides a function to set outline level of a single // column by given worksheet name and column name. The value of parameter // 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2: // // err := f.SetColOutlineLevel("Sheet1", "D", 2) func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error { if level > 7 || level < 1 { return ErrOutlineLevel } colNum, err := ColumnNameToNumber(col) if err != nil { return err } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.setColOutlineLevel(colNum, level) return err } // setColOutlineLevel provides a function to set the outline level of a single // column by given column number. func (ws *xlsxWorksheet) setColOutlineLevel(colNum int, level uint8) { colData := xlsxCol{ Min: colNum, Max: colNum, OutlineLevel: level, CustomWidth: true, } if ws.Cols == nil { cols := xlsxCols{} cols.Col = append(cols.Col, colData) ws.Cols = &cols return } ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { fc.BestFit = c.BestFit fc.Collapsed = c.Collapsed fc.CustomWidth = c.CustomWidth fc.Hidden = c.Hidden fc.Phonetic = c.Phonetic fc.Style = c.Style fc.Width = c.Width return fc }) } // SetColStyle provides a function to set style of columns by given worksheet // name, columns range and style ID. This function is concurrency safe. Note // that this will overwrite the existing styles for the columns, it won't // append or merge style with existing styles. // // For example set style of column H on Sheet1: // // err = f.SetColStyle("Sheet1", "H", style) // // Set style of columns C:F on Sheet1: // // err = f.SetColStyle("Sheet1", "C:F", style) func (f *File) SetColStyle(sheet, columns string, styleID int) error { minVal, maxVal, err := f.parseColRange(columns) if err != nil { return err } f.mu.Lock() s, err := f.stylesReader() if err != nil { f.mu.Unlock() return err } ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() s.mu.Lock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { s.mu.Unlock() return newInvalidStyleID(styleID) } s.mu.Unlock() ws.mu.Lock() ws.setColStyle(minVal, maxVal, styleID) ws.mu.Unlock() if rows := len(ws.SheetData.Row); rows > 0 { for col := minVal; col <= maxVal; col++ { from, _ := CoordinatesToCellName(col, 1) to, _ := CoordinatesToCellName(col, rows) err = f.SetCellStyle(sheet, from, to, styleID) } } return err } // setColStyle provides a function to set the style of a single column or // multiple columns. func (ws *xlsxWorksheet) setColStyle(minVal, maxVal, styleID int) { if ws.Cols == nil { ws.Cols = &xlsxCols{} } width := defaultColWidth if ws.SheetFormatPr != nil && ws.SheetFormatPr.DefaultColWidth > 0 { width = ws.SheetFormatPr.DefaultColWidth } ws.Cols.Col = flatCols(xlsxCol{ Min: minVal, Max: maxVal, Width: float64Ptr(width), Style: styleID, }, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { fc.BestFit = c.BestFit fc.Collapsed = c.Collapsed fc.CustomWidth = c.CustomWidth fc.Hidden = c.Hidden fc.OutlineLevel = c.OutlineLevel fc.Phonetic = c.Phonetic fc.Width = c.Width return fc }) } // SetColWidth provides a function to set the width of a single column or // multiple columns. This function is concurrency safe. For example: // // err := f.SetColWidth("Sheet1", "A", "H", 20) func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error { minVal, maxVal, err := f.parseColRange(startCol + ":" + endCol) if err != nil { return err } if width > MaxColumnWidth { return ErrColumnWidth } f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() ws.setColWidth(minVal, maxVal, width) return err } // setColWidth provides a function to set the width of a single column or // multiple columns. func (ws *xlsxWorksheet) setColWidth(minVal, maxVal int, width float64) { col := xlsxCol{ Min: minVal, Max: maxVal, Width: float64Ptr(width), CustomWidth: true, } if ws.Cols == nil { cols := xlsxCols{} cols.Col = append(cols.Col, col) ws.Cols = &cols return } ws.Cols.Col = flatCols(col, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { fc.BestFit = c.BestFit fc.Collapsed = c.Collapsed fc.Hidden = c.Hidden fc.OutlineLevel = c.OutlineLevel fc.Phonetic = c.Phonetic fc.Style = c.Style return fc }) } // flatCols provides a method for the column's operation functions to flatten // and check the worksheet columns. func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol { var fc []xlsxCol for i := col.Min; i <= col.Max; i++ { var c xlsxCol _ = deepcopy.Copy(&c, col) c.Min, c.Max = i, i fc = append(fc, c) } inFlat := func(colID int, cols []xlsxCol) (int, bool) { for idx, c := range cols { if c.Max == colID && c.Min == colID { return idx, true } } return -1, false } for _, column := range cols { for i := column.Min; i <= column.Max; i++ { if idx, ok := inFlat(i, fc); ok { fc[idx] = replacer(fc[idx], column) continue } var c xlsxCol _ = deepcopy.Copy(&c, column) c.Min, c.Max = i, i fc = append(fc, c) } } return fc } // positionObjectPixels calculate the vertices that define the position of a // graphical object within the worksheet in pixels. // // +------------+------------+ // | A | B | // +-----+------------+------------+ // | |(x1,y1) | | // | 1 |(A1)._______|______ | // | | | | | // | | | | | // +-----+----| OBJECT |-----+ // | | | | | // | 2 | |______________. | // | | | (B2)| // | | | (x2,y2)| // +-----+------------+------------+ // // Example of an object that covers some range reference from cell A1 to B2. // // Based on the width and height of the object we need to calculate 8 vars: // // colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2. // // We also calculate the absolute x and y position of the top left vertex of // the object. This is required for images. // // The width and height of the cells that the object occupies can be // variable and have to be taken into account. // // The values of col_start and row_start are passed in from the calling // function. The values of col_end and row_end are calculated by // subtracting the width and height of the object from the width and // height of the underlying cells. // // colStart # Col containing upper left corner of object. // x1 # Distance to left side of object. // // rowStart # Row containing top left corner of object. // y1 # Distance to top of object. // // colEnd # Col containing lower right corner of object. // x2 # Distance to right side of object. // // rowEnd # Row containing bottom right corner of object. // y2 # Distance to bottom of object. // // width # Width of object frame. // height # Height of object frame. func (f *File) positionObjectPixels(sheet string, col, row, width, height int, opts *GraphicOptions) (int, int, int, int, int, int, int, int) { colIdx, rowIdx := col-1, row-1 // Initialized end cell to the same as the start cell. colEnd, rowEnd := colIdx, rowIdx x1, y1, x2, y2 := opts.OffsetX, opts.OffsetY, width, height if opts.Positioning != "oneCell" { // Using a twoCellAnchor, the maximum possible offset is limited by the // "from" cell dimensions. If these were to be exceeded the "toPoint" would // be calculated incorrectly, since the requested "fromPoint" is not possible x1 = min(x1, f.getColWidth(sheet, col)) y1 = min(y1, f.getRowHeight(sheet, row)) x2 += x1 y2 += y1 // Subtract the underlying cell widths to find end cell of the object. for x2 >= f.getColWidth(sheet, colEnd+1) { colEnd++ x2 -= f.getColWidth(sheet, colEnd) } // Subtract the underlying cell heights to find end cell of the object. for y2 >= f.getRowHeight(sheet, rowEnd+1) { rowEnd++ y2 -= f.getRowHeight(sheet, rowEnd) } } // The end vertices are whatever is left from the width and height. return colIdx, rowIdx, colEnd, rowEnd, x1, y1, x2, y2 } // getColWidth provides a function to get column width in pixels by given // sheet name and column number. func (f *File) getColWidth(sheet string, col int) int { ws, _ := f.workSheetReader(sheet) ws.mu.Lock() defer ws.mu.Unlock() if ws.Cols != nil { width := -1.0 for _, v := range ws.Cols.Col { if v.Min <= col && col <= v.Max && v.Width != nil { width = *v.Width break } } if width != -1.0 { return int(convertColWidthToPixels(width)) } } if ws.SheetFormatPr != nil { if ws.SheetFormatPr.DefaultColWidth > 0 { return int(convertColWidthToPixels(ws.SheetFormatPr.DefaultColWidth)) } if ws.SheetFormatPr.BaseColWidth > 0 { return int(convertColWidthToPixels(float64(ws.SheetFormatPr.BaseColWidth))) + 5 } } // Optimization for when the column widths haven't changed. return int(defaultColWidthPixels) } // GetColStyle provides a function to get column style ID by given worksheet // name and column name. This function is concurrency safe. func (f *File) GetColStyle(sheet, col string) (int, error) { var styleID int colNum, err := ColumnNameToNumber(col) if err != nil { return styleID, err } f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return styleID, err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() if ws.Cols != nil { for _, v := range ws.Cols.Col { if v.Min <= colNum && colNum <= v.Max { styleID = v.Style } } } return styleID, err } // GetColWidth provides a function to get column width by given worksheet name // and column name. This function is concurrency safe. func (f *File) GetColWidth(sheet, col string) (float64, error) { colNum, err := ColumnNameToNumber(col) if err != nil { return defaultColWidth, err } f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return defaultColWidth, err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() if ws.Cols != nil { var width float64 for _, v := range ws.Cols.Col { if v.Min <= colNum && colNum <= v.Max && v.Width != nil { width = *v.Width } } if width != 0 { return width, err } } if ws.SheetFormatPr != nil { if ws.SheetFormatPr.DefaultColWidth > 0 { return ws.SheetFormatPr.DefaultColWidth, err } if ws.SheetFormatPr.BaseColWidth > 0 { return float64(ws.SheetFormatPr.BaseColWidth), err } } // Optimization for when the column widths haven't changed. return defaultColWidth, err } // InsertCols provides a function to insert new columns before the given column // name and number of columns. For example, create two columns before column // C in Sheet1: // // err := f.InsertCols("Sheet1", "C", 2) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) InsertCols(sheet, col string, n int) error { num, err := ColumnNameToNumber(col) if err != nil { return err } if n < 1 || n > MaxColumns { return ErrColumnNumber } return f.adjustHelper(sheet, columns, num, n) } // RemoveCol provides a function to remove single column by given worksheet // name and column index. For example, remove column C in Sheet1: // // err := f.RemoveCol("Sheet1", "C") // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) RemoveCol(sheet, col string) error { num, err := ColumnNameToNumber(col) if err != nil { return err } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.formulaSI.Clear() for rowIdx := range ws.SheetData.Row { rowData := &ws.SheetData.Row[rowIdx] for colIdx := range rowData.C { colName, _, _ := SplitCellName(rowData.C[colIdx].R) if colName == col { rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)[:len(rowData.C)-1] break } } } return f.adjustHelper(sheet, columns, num, -1) } // convertColWidthToPixels provides function to convert the width of a cell // from user's units to pixels. Excel rounds the column width to the nearest // pixel. If the width hasn't been set by the user we use the default value. // If the column is hidden it has a value of zero. func convertColWidthToPixels(width float64) float64 { var pixels float64 var maxDigitWidth float64 = 8 if width == 0 { return pixels } pixels = (width*maxDigitWidth + 0.5) return float64(int(pixels)) } // calcTextWidth calculates the column width needed to display a text based on // the font family, font size, font weight and italic format. func (fnt *Font) calcTextWidth(text string) float64 { var lowerUnits, upperUnits, wideUnits float64 for _, r := range text { switch width.LookupRune(r).Kind() { case width.EastAsianWide, width.EastAsianFullwidth: wideUnits += 2 default: if unicode.IsUpper(r) { upperUnits++ continue } lowerUnits++ } } sz := fnt.Size if sz <= 0 { sz = defaultFontSize } lowerFactor, upperFactor, wideFactor := 1.0, 1.0, 1.0 if fw, ok := supportedFontWidthFactors[strings.ToLower(fnt.Family)]; ok { lowerFactor, upperFactor, wideFactor = fw[0], fw[1], fw[2] } w := (lowerUnits*lowerFactor + upperUnits*upperFactor + wideUnits*wideFactor) * (sz / defaultFontSize) if fnt.Bold { w *= 1.05 } if fnt.Italic { w *= 1.05 } if inStrSlice(supportedVertAlignTypes, fnt.VertAlign, true) != -1 { w *= 0.6 } return w } // calcRichTextWidth calculates the column width needed to display a rich text // based on the font format. func (fnt *Font) calcRichTextWidth(runs []RichTextRun) float64 { var w, width float64 for _, run := range runs { if run.Font != nil { fnt = run.Font } if i := strings.IndexAny(run.Text, "\r\n"); i >= 0 { first := run.Text[:i] rest := run.Text[i+1:] if w += fnt.calcTextWidth(first); w > width { width = w } w = fnt.calcTextWidth(rest) continue } w += fnt.calcTextWidth(run.Text) } if w > width { width = w } return width } // autoFitColWidth provides a function to auto fit columns width according to // their text content with default font size and font. func (f *File) autoFitColWidth(sheet string, col, rows int, defaultFnt *Font) (float64, error) { var width float64 for row := 1; row <= rows; row++ { cell, err := CoordinatesToCellName(col, row) if err != nil { return width, err } val, err := f.CalcCellValue(sheet, cell) if err != nil && inStrSlice([]string{ formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM, formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA, }, err.Error(), true) != -1 { val = err.Error() } if val == "" { continue } styleID, _ := f.GetCellStyle(sheet, cell) fnt := defaultFnt style, err := f.GetStyle(styleID) if err != nil { return width, err } if style != nil && style.Font != nil { fnt = style.Font } if cellType, _ := f.GetCellType(sheet, cell); cellType == CellTypeInlineString || cellType == CellTypeSharedString { runs, _ := f.GetCellRichText(sheet, cell) if w := fnt.calcRichTextWidth(runs); w > width { width = w } continue } if w := fnt.calcTextWidth(val); w > width { width = w } } return width, nil } // AutoFitColWidth provides a function to auto fit columns width according to // their text content with font format. If the selected range contains hidden // columns and those columns have content, this function will unhide the hidden // columns. Not that this function calculates the width of the text // approximately based on the font format, currently does not support merged // cells. the actual width may be different when you open the workbook in Office // applications. This process can be relatively slow on large worksheets, so // this should normally only be called once per column, at the end of your // processing. // // For example, auto fit column width for column D on Sheet1: // // err := f.AutoFitColWidth("Sheet1", "D") // // Auto fit column width for columns D to F on Sheet1: // // err := f.AutoFitColWidth("Sheet1", "D:F") func (f *File) AutoFitColWidth(sheet, columns string) error { minVal, maxVal, err := f.parseColRange(columns) if err != nil { return err } rows, err := f.GetRows(sheet) if err != nil { return err } defaultFnt := &Font{} font, err := f.readDefaultFont() if err != nil { return err } if font != nil { if font.Sz != nil && font.Sz.Val != nil { defaultFnt.Size = *font.Sz.Val } if font.Name != nil && font.Name.Val != nil { defaultFnt.Family = *font.Name.Val } } ws, _ := f.workSheetReader(sheet) for col := minVal; col <= maxVal; col++ { if width, _ := f.autoFitColWidth(sheet, col, len(rows), defaultFnt); width > 0 { width += 2 if width > MaxColumnWidth { width = MaxColumnWidth } ws.setColVisible(col, col, true) ws.setColWidth(col, col, width) } } return err } ================================================ FILE: col_test.go ================================================ package excelize import ( "fmt" "path/filepath" "sort" "strings" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCols(t *testing.T) { const sheet2 = "Sheet2" f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } cols, err := f.Cols(sheet2) if !assert.NoError(t, err) { t.FailNow() } var collectedRows [][]string for cols.Next() { rows, err := cols.Rows() assert.NoError(t, err) collectedRows = append(collectedRows, trimSliceSpace(rows)) } if !assert.NoError(t, cols.Error()) { t.FailNow() } returnedColumns, err := f.GetCols(sheet2) assert.NoError(t, err) for i := range returnedColumns { returnedColumns[i] = trimSliceSpace(returnedColumns[i]) } if !assert.Equal(t, collectedRows, returnedColumns) { t.FailNow() } assert.NoError(t, f.Close()) f = NewFile() cells := []string{"C2", "C3", "C4"} for _, cell := range cells { assert.NoError(t, f.SetCellValue("Sheet1", cell, 1)) } _, err = f.Rows("Sheet1") assert.NoError(t, err) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ Dimension: &xlsxDimension{ Ref: "C2:C4", }, }) _, err = f.Rows("Sheet1") assert.NoError(t, err) // Test columns iterator with invalid sheet name _, err = f.Cols("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test get columns cells with invalid sheet name _, err = f.GetCols("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test columns iterator with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) cols, err = f.Cols("Sheet1") assert.NoError(t, err) cols.Next() _, err = cols.Rows() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B`)) f.checked = sync.Map{} _, err = f.Cols("Sheet1") assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B`)) _, err = f.Cols("Sheet1") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestColumnsIterator(t *testing.T) { sheetName, colCount, expectedNumCol := "Sheet2", 0, 9 f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) require.NoError(t, err) cols, err := f.Cols(sheetName) require.NoError(t, err) for cols.Next() { colCount++ require.True(t, colCount <= expectedNumCol, "colCount is greater than expected") } assert.Equal(t, expectedNumCol, colCount) assert.NoError(t, f.Close()) f, sheetName, colCount, expectedNumCol = NewFile(), "Sheet1", 0, 4 cells := []string{"C2", "C3", "C4", "D2", "D3", "D4"} for _, cell := range cells { assert.NoError(t, f.SetCellValue(sheetName, cell, 1)) } cols, err = f.Cols(sheetName) require.NoError(t, err) for cols.Next() { colCount++ require.True(t, colCount <= 4, "colCount is greater than expected") } assert.Equal(t, expectedNumCol, colCount) } func TestColsError(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } _, err = f.Cols("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) } func TestGetColsError(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } _, err = f.GetCols("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`B`, NameSpaceSpreadSheet.Value))) f.checked = sync.Map{} _, err = f.GetCols("Sheet1") assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`B`, NameSpaceSpreadSheet.Value))) _, err = f.GetCols("Sheet1") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) f = NewFile() cols, err := f.Cols("Sheet1") assert.NoError(t, err) cols.totalRows = 2 cols.totalCols = 2 cols.curCol = 1 cols.sheetXML = []byte(fmt.Sprintf(`A`, NameSpaceSpreadSheet.Value)) _, err = cols.Rows() assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) f.Pkg.Store("xl/worksheets/sheet1.xml", nil) f.Sheet.Store("xl/worksheets/sheet1.xml", nil) _, err = f.Cols("Sheet1") assert.NoError(t, err) } func TestColsRows(t *testing.T) { f := NewFile() _, err := f.Cols("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1)) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ Dimension: &xlsxDimension{ Ref: "A1:A1", }, }) f = NewFile() f.Pkg.Store("xl/worksheets/sheet1.xml", nil) _, err = f.Cols("Sheet1") if !assert.NoError(t, err) { t.FailNow() } f = NewFile() cols, err := f.Cols("Sheet1") if !assert.NoError(t, err) { t.FailNow() } _, err = cols.Rows() assert.NoError(t, err) cols.stashCol, cols.curCol = 0, 1 // Test if token is nil cols.sheetXML = nil _, err = cols.Rows() assert.NoError(t, err) } func TestColumnVisibility(t *testing.T) { t.Run("TestBook1", func(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) // Hide/display a column with SetColVisible assert.NoError(t, f.SetColVisible("Sheet1", "F", false)) assert.NoError(t, f.SetColVisible("Sheet1", "F", true)) visible, err := f.GetColVisible("Sheet1", "F") assert.Equal(t, true, visible) assert.NoError(t, err) // Test hiding a few columns SetColVisible(...false)... assert.NoError(t, f.SetColVisible("Sheet1", "F:V", false)) visible, err = f.GetColVisible("Sheet1", "F") assert.Equal(t, false, visible) assert.NoError(t, err) visible, err = f.GetColVisible("Sheet1", "U") assert.Equal(t, false, visible) assert.NoError(t, err) visible, err = f.GetColVisible("Sheet1", "V") assert.Equal(t, false, visible) assert.NoError(t, err) // ...and displaying them back SetColVisible(...true) assert.NoError(t, f.SetColVisible("Sheet1", "V:F", true)) visible, err = f.GetColVisible("Sheet1", "F") assert.Equal(t, true, visible) assert.NoError(t, err) visible, err = f.GetColVisible("Sheet1", "U") assert.Equal(t, true, visible) assert.NoError(t, err) visible, err = f.GetColVisible("Sheet1", "G") assert.Equal(t, true, visible) assert.NoError(t, err) // Test get column visible on not exists worksheet _, err = f.GetColVisible("SheetN", "F") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get column visible with invalid sheet name _, err = f.GetColVisible("Sheet:1", "F") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test get column visible with illegal cell reference _, err = f.GetColVisible("Sheet1", "*") assert.EqualError(t, err, newInvalidColumnNameError("*").Error()) assert.EqualError(t, f.SetColVisible("Sheet1", "*", false), newInvalidColumnNameError("*").Error()) // Test set column visible with invalid sheet name assert.EqualError(t, f.SetColVisible("Sheet:1", "A", false), ErrSheetNameInvalid.Error()) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) assert.NoError(t, f.SetColVisible("Sheet3", "E", false)) assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), newInvalidColumnNameError("-1").Error()) assert.EqualError(t, f.SetColVisible("SheetN", "E", false), "sheet SheetN does not exist") assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColumnVisibility.xlsx"))) }) t.Run("TestBook3", func(t *testing.T) { f, err := prepareTestBook3() assert.NoError(t, err) visible, err := f.GetColVisible("Sheet1", "B") assert.Equal(t, true, visible) assert.NoError(t, err) }) } func TestOutlineLevel(t *testing.T) { f := NewFile() level, err := f.GetColOutlineLevel("Sheet1", "D") assert.Equal(t, uint8(0), level) assert.NoError(t, err) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4)) level, err = f.GetColOutlineLevel("Sheet1", "D") assert.Equal(t, uint8(4), level) assert.NoError(t, err) level, err = f.GetColOutlineLevel("SheetN", "A") assert.Equal(t, uint8(0), level) assert.EqualError(t, err, "sheet SheetN does not exist") // Test column outline level with invalid sheet name _, err = f.GetColOutlineLevel("Sheet:1", "A") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.NoError(t, f.SetColWidth("Sheet2", "A", "D", 13)) assert.EqualError(t, f.SetColWidth("Sheet2", "A", "D", MaxColumnWidth+1), ErrColumnWidth.Error()) // Test set column width with invalid sheet name assert.EqualError(t, f.SetColWidth("Sheet:1", "A", "D", 13), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2)) assert.NoError(t, f.SetRowOutlineLevel("Sheet1", 2, 7)) assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "D", 8), ErrOutlineLevel.Error()) assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 2, 8), ErrOutlineLevel.Error()) // Test set row outline level on not exists worksheet assert.EqualError(t, f.SetRowOutlineLevel("SheetN", 1, 4), "sheet SheetN does not exist") // Test set row outline level with invalid sheet name assert.EqualError(t, f.SetRowOutlineLevel("Sheet:1", 1, 4), ErrSheetNameInvalid.Error()) // Test get row outline level on not exists worksheet _, err = f.GetRowOutlineLevel("SheetN", 1) assert.EqualError(t, err, "sheet SheetN does not exist") // Test get row outline level with invalid sheet name _, err = f.GetRowOutlineLevel("Sheet:1", 1) assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test set and get column outline level with illegal cell reference assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "*", 1), newInvalidColumnNameError("*").Error()) _, err = f.GetColOutlineLevel("Sheet1", "*") assert.EqualError(t, err, newInvalidColumnNameError("*").Error()) // Test set column outline level on not exists worksheet assert.EqualError(t, f.SetColOutlineLevel("SheetN", "E", 2), "sheet SheetN does not exist") assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 0, 1), newInvalidRowNumberError(0).Error()) level, err = f.GetRowOutlineLevel("Sheet1", 2) assert.NoError(t, err) assert.Equal(t, uint8(7), level) _, err = f.GetRowOutlineLevel("Sheet1", 0) assert.EqualError(t, err, newInvalidRowNumberError(0).Error()) level, err = f.GetRowOutlineLevel("Sheet1", 10) assert.NoError(t, err) assert.Equal(t, uint8(0), level) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx"))) f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2)) assert.NoError(t, f.Close()) } func TestSetColStyle(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello")) styleID, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"94D3A2"}, Pattern: 1}}) assert.NoError(t, err) // Test set column style on not exists worksheet assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN does not exist") // Test set column style with illegal column name assert.EqualError(t, f.SetColStyle("Sheet1", "*", styleID), newInvalidColumnNameError("*").Error()) assert.EqualError(t, f.SetColStyle("Sheet1", "A:*", styleID), newInvalidColumnNameError("*").Error()) // Test set column style with invalid style ID assert.EqualError(t, f.SetColStyle("Sheet1", "B", -1), newInvalidStyleID(-1).Error()) // Test set column style with not exists style ID assert.EqualError(t, f.SetColStyle("Sheet1", "B", 10), newInvalidStyleID(10).Error()) // Test set column style with invalid sheet name assert.EqualError(t, f.SetColStyle("Sheet:1", "A", 0), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID)) style, err := f.GetColStyle("Sheet1", "B") assert.NoError(t, err) assert.Equal(t, styleID, style) // Test set column style with already exists column with style assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID)) assert.NoError(t, f.SetColStyle("Sheet1", "D:C", styleID)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[1].C[2].S = 0 cellStyleID, err := f.GetCellStyle("Sheet1", "C2") assert.NoError(t, err) assert.Equal(t, styleID, cellStyleID) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx"))) // Test set column style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.SetColStyle("Sheet1", "C:F", styleID), "XML syntax error on line 1: invalid UTF-8") // Test set column style with worksheet properties columns default width settings f = NewFile() assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{DefaultColWidth: float64Ptr(20)})) style, err = f.NewStyle(&Style{Alignment: &Alignment{Vertical: "center"}}) assert.NoError(t, err) assert.NoError(t, f.SetColStyle("Sheet1", "A:Z", style)) width, err := f.GetColWidth("Sheet1", "B") assert.NoError(t, err) assert.Equal(t, 20.0, width) } func TestColWidth(t *testing.T) { f := NewFile() assert.NoError(t, f.SetColWidth("Sheet1", "B", "A", 12)) assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 12)) width, err := f.GetColWidth("Sheet1", "A") assert.Equal(t, float64(12), width) assert.NoError(t, err) width, err = f.GetColWidth("Sheet1", "C") assert.Equal(t, defaultColWidth, width) assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetFormatPr = &xlsxSheetFormatPr{DefaultColWidth: 10} ws.(*xlsxWorksheet).Cols = nil width, err = f.GetColWidth("Sheet1", "A") assert.NoError(t, err) assert.Equal(t, 10.0, width) assert.Equal(t, 80, f.getColWidth("Sheet1", 1)) // Test set and get column width with illegal cell reference width, err = f.GetColWidth("Sheet1", "*") assert.Equal(t, defaultColWidth, width) assert.EqualError(t, err, newInvalidColumnNameError("*").Error()) assert.EqualError(t, f.SetColWidth("Sheet1", "*", "B", 1), newInvalidColumnNameError("*").Error()) assert.EqualError(t, f.SetColWidth("Sheet1", "A", "*", 1), newInvalidColumnNameError("*").Error()) // Test set column width on not exists worksheet assert.EqualError(t, f.SetColWidth("SheetN", "B", "A", 12), "sheet SheetN does not exist") // Test get column width on not exists worksheet _, err = f.GetColWidth("SheetN", "A") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get column width invalid sheet name _, err = f.GetColWidth("Sheet:1", "A") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColWidth.xlsx"))) convertRowHeightToPixels(0) } func TestGetColStyle(t *testing.T) { f := NewFile() styleID, err := f.GetColStyle("Sheet1", "A") assert.NoError(t, err) assert.Equal(t, styleID, 0) // Test get column style on not exists worksheet _, err = f.GetColStyle("SheetN", "A") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get column style with illegal column name _, err = f.GetColStyle("Sheet1", "*") assert.EqualError(t, err, newInvalidColumnNameError("*").Error()) // Test get column style with invalid sheet name _, err = f.GetColStyle("Sheet:1", "A") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) } func TestInsertCols(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) assert.NoError(t, fillCells(f, sheet1, 10, 10)) assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External")) assert.NoError(t, f.MergeCell(sheet1, "A1", "C3")) assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", []AutoFilterOptions{{Column: "B", Expression: "x != blanks"}})) assert.NoError(t, f.InsertCols(sheet1, "A", 1)) // Test insert column with illegal cell reference assert.EqualError(t, f.InsertCols(sheet1, "*", 1), newInvalidColumnNameError("*").Error()) // Test insert column with invalid sheet name assert.EqualError(t, f.InsertCols("Sheet:1", "A", 1), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.InsertCols(sheet1, "A", 0), ErrColumnNumber.Error()) assert.EqualError(t, f.InsertCols(sheet1, "A", MaxColumns), ErrColumnNumber.Error()) assert.EqualError(t, f.InsertCols(sheet1, "A", MaxColumns-10), ErrColumnNumber.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertCols.xlsx"))) } func TestRemoveCol(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) assert.NoError(t, fillCells(f, sheet1, 10, 15)) assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External")) assert.NoError(t, f.SetCellHyperLink(sheet1, "C5", "https://github.com", "External")) assert.NoError(t, f.MergeCell(sheet1, "A1", "B1")) assert.NoError(t, f.MergeCell(sheet1, "A2", "B2")) assert.NoError(t, f.RemoveCol(sheet1, "A")) assert.NoError(t, f.RemoveCol(sheet1, "A")) // Test remove column with illegal cell reference assert.EqualError(t, f.RemoveCol("Sheet1", "*"), newInvalidColumnNameError("*").Error()) // Test remove column on not exists worksheet assert.EqualError(t, f.RemoveCol("SheetN", "B"), "sheet SheetN does not exist") // Test remove column with invalid sheet name assert.EqualError(t, f.RemoveCol("Sheet:1", "A"), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx"))) } func TestConvertColWidthToPixels(t *testing.T) { assert.Equal(t, -7.0, convertColWidthToPixels(-1)) } func TestAutoFitColWidth(t *testing.T) { f := NewFile() assert.NoError(t, f.SetColVisible("Sheet1", "A:C", false)) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "SUBSTITUTE(\"1 \",\" \",\"\")")) assert.NoError(t, f.AutoFitColWidth("Sheet1", "A:B")) assert.NoError(t, f.SetCellValue("Sheet1", "C1", 1234567890)) assert.NoError(t, f.AutoFitColWidth("Sheet1", "C")) visible, err := f.GetColVisible("Sheet1", "A") assert.NoError(t, err) assert.True(t, visible) visible, err = f.GetColVisible("Sheet1", "B") assert.NoError(t, err) assert.False(t, visible) visible, err = f.GetColVisible("Sheet1", "C") assert.NoError(t, err) assert.True(t, visible) styleID, err := f.NewStyle(&Style{Font: &Font{Family: "Microsoft YaHei", Bold: true, Italic: true, Charset: intPtr(134)}}) assert.NoError(t, err) assert.NoError(t, f.SetColStyle("Sheet1", "D", styleID)) assert.NoError(t, f.SetCellValue("Sheet1", "D1", 1234567890)) assert.NoError(t, f.SetCellValue("Sheet1", "D2", "文本1234567890")) assert.NoError(t, f.AutoFitColWidth("Sheet1", "D")) assert.NoError(t, f.SetCellValue("Sheet1", "E1", "text")) assert.NoError(t, f.SetCellValue("Sheet1", "E2", "文本********")) assert.NoError(t, f.AutoFitColWidth("Sheet1", "E")) styleID, err = f.NewStyle(&Style{Font: &Font{Size: 50}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "F1", "F1", styleID)) assert.NoError(t, f.SetCellValue("Sheet1", "F1", "Text")) assert.NoError(t, f.AutoFitColWidth("Sheet1", "F")) assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "G1", "1/0")) assert.NoError(t, f.SetCellFormula("Sheet1", "G2", "1^\"text\"")) assert.NoError(t, f.AutoFitColWidth("Sheet1", "G:G")) names := make([]string, 0, len(supportedFontWidthFactors)) for name := range supportedFontWidthFactors { names = append(names, name) } sort.Strings(names) for idx, name := range names { col, err := ColumnNumberToName(idx + 8) assert.NoError(t, err) styleID, err := f.NewStyle(&Style{Font: &Font{Family: name}}) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", col+"1", name)) assert.NoError(t, f.SetCellValue("Sheet1", col+"2", strings.ToUpper(name))) assert.NoError(t, f.SetCellStyle("Sheet1", col+"1", col+"2", styleID)) } assert.NoError(t, f.AutoFitColWidth("Sheet1", "H:KP")) assert.NoError(t, f.SetCellValue("Sheet1", "KQ1", strings.Repeat("s", TotalCellChars))) assert.NoError(t, f.AutoFitColWidth("Sheet1", "KQ")) assert.NoError(t, err) width, err := f.GetColWidth("Sheet1", "KQ") assert.NoError(t, err) assert.Equal(t, float64(MaxColumnWidth), width) assert.Equal(t, f.AutoFitColWidth("Sheet1", ""), newInvalidColumnNameError("")) assert.Equal(t, f.AutoFitColWidth("SheetN", "A"), ErrSheetNotExist{"SheetN"}) _, err = f.autoFitColWidth("Sheet1", TotalRows, 1, &Font{}) assert.Equal(t, ErrColumnNumber, err) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAutoFitColWidth.xlsx"))) f = NewFile() // Test auto fit column width with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.AutoFitColWidth("Sheet1", "A"), "XML syntax error on line 1: invalid UTF-8") // Test auto fit column width with invalid cell style ID f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ SheetData: xlsxSheetData{Row: []xlsxRow{ {R: 1, C: []xlsxC{{R: "A1", S: 1, V: "Test"}}}, }}, }) _, err = f.autoFitColWidth("Sheet1", 1, 1, &Font{}) assert.Equal(t, err, newInvalidStyleID(1)) } ================================================ FILE: crypt.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/rand" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/binary" "encoding/xml" "hash" "math" "path/filepath" "reflect" "sort" "strings" "github.com/richardlehane/mscfb" "golang.org/x/crypto/md4" "golang.org/x/crypto/ripemd160" "golang.org/x/text/encoding/unicode" ) var ( blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption oleIdentifier = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1} headerCLSID = make([]byte, 16) difSect = -4 endOfChain = -2 fatSect = -3 iterCount = 50000 packageEncryptionChunkSize = 4096 packageOffset = 8 // First 8 bytes are the size of the stream sheetProtectionSpinCount = 1e5 workbookProtectionSpinCount = 1e5 ) // Encryption specifies the encryption structure, streams, and storages are // required when encrypting ECMA-376 documents. type Encryption struct { XMLName xml.Name `xml:"encryption"` KeyData KeyData `xml:"keyData"` DataIntegrity DataIntegrity `xml:"dataIntegrity"` KeyEncryptors KeyEncryptors `xml:"keyEncryptors"` } // KeyData specifies the cryptographic attributes used to encrypt the data. type KeyData struct { SaltSize int `xml:"saltSize,attr"` BlockSize int `xml:"blockSize,attr"` KeyBits int `xml:"keyBits,attr"` HashSize int `xml:"hashSize,attr"` CipherAlgorithm string `xml:"cipherAlgorithm,attr"` CipherChaining string `xml:"cipherChaining,attr"` HashAlgorithm string `xml:"hashAlgorithm,attr"` SaltValue string `xml:"saltValue,attr"` } // DataIntegrity specifies the encrypted copies of the salt and hash values // used to help ensure that the integrity of the encrypted data has not been // compromised. type DataIntegrity struct { EncryptedHmacKey string `xml:"encryptedHmacKey,attr"` EncryptedHmacValue string `xml:"encryptedHmacValue,attr"` } // KeyEncryptors specifies the key encryptors used to encrypt the data. type KeyEncryptors struct { KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"` } // KeyEncryptor specifies that the schema used by this encryptor is the schema // specified for password-based encryptors. type KeyEncryptor struct { XMLName xml.Name `xml:"keyEncryptor"` URI string `xml:"uri,attr"` EncryptedKey EncryptedKey `xml:"encryptedKey"` } // EncryptedKey used to generate the encrypting key. type EncryptedKey struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"` SpinCount int `xml:"spinCount,attr"` EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"` EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"` EncryptedKeyValue string `xml:"encryptedKeyValue,attr"` KeyData } // StandardEncryptionHeader structure is used by ECMA-376 document encryption // [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify // encryption properties for an encrypted stream. type StandardEncryptionHeader struct { Flags uint32 SizeExtra uint32 AlgID uint32 AlgIDHash uint32 KeySize uint32 ProviderType uint32 Reserved1 uint32 Reserved2 uint32 CspName string } // StandardEncryptionVerifier structure is used by Office Binary Document RC4 // CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this // structure MUST specify the hashing algorithm and encryption algorithm used // in the EncryptionVerifier structure. type StandardEncryptionVerifier struct { SaltSize uint32 Salt []byte EncryptedVerifier []byte VerifierHashSize uint32 EncryptedVerifierHash []byte } // encryptionInfo structure is used for standard encryption with SHA1 // cryptographic algorithm. type encryption struct { BlockSize, SaltSize int EncryptedKeyValue, EncryptedVerifierHashInput, EncryptedVerifierHashValue, SaltValue []byte KeyBits uint32 } // Decrypt API decrypts the CFB file format with ECMA-376 agile encryption and // standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160, // SHA1, SHA256, SHA384 and SHA512 currently. func Decrypt(raw []byte, opts *Options) (packageBuf []byte, err error) { doc, err := mscfb.New(bytes.NewReader(raw)) if err != nil { return } var encryptionInfoBuf, encryptedPackageBuf []byte if encryptionInfoBuf, encryptedPackageBuf, err = extractPart(doc); err != nil { return } mechanism, err := encryptionMechanism(encryptionInfoBuf) if err != nil || mechanism == "extensible" { return } if mechanism == "agile" { return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opts) } return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opts) } // Encrypt API encrypt data with the password. func Encrypt(raw []byte, opts *Options) ([]byte, error) { encryptor := encryption{ EncryptedVerifierHashInput: make([]byte, 16), EncryptedVerifierHashValue: make([]byte, 32), SaltValue: make([]byte, 16), BlockSize: 16, KeyBits: 128, SaltSize: 16, } // Key Encryption encryptionInfoBuffer, err := encryptor.standardKeyEncryption(opts.Password) if err != nil { return nil, err } // Package Encryption encryptedPackage := make([]byte, 8) binary.LittleEndian.PutUint64(encryptedPackage, uint64(len(raw))) encryptedPackage = append(encryptedPackage, encryptor.encrypt(raw)...) // Create a new CFB compoundFile := &cfb{ paths: []string{"Root Entry/"}, sectors: []sector{{name: "Root Entry", typeID: 5}}, } compoundFile.put("EncryptionInfo", encryptionInfoBuffer) compoundFile.put("EncryptedPackage", encryptedPackage) return compoundFile.write(), nil } // extractPart extract data from storage by specified part name. func extractPart(doc *mscfb.Reader) ([]byte, []byte, error) { var encryptionInfoBuf, encryptedPackageBuf []byte for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { switch entry.Name { case "EncryptionInfo": buf := make([]byte, entry.Size) if _, err := doc.Read(buf); err != nil { return encryptionInfoBuf, encryptedPackageBuf, err } encryptionInfoBuf = buf case "EncryptedPackage": buf := make([]byte, entry.Size) if _, err := doc.Read(buf); err != nil { return encryptionInfoBuf, encryptedPackageBuf, err } encryptedPackageBuf = buf } } return encryptionInfoBuf, encryptedPackageBuf, nil } // encryptionMechanism parse password-protected documents created mechanism. func encryptionMechanism(buffer []byte) (mechanism string, err error) { if len(buffer) < 4 { err = ErrUnknownEncryptMechanism return } versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4]) if versionMajor == 4 && versionMinor == 4 { mechanism = "agile" return } else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 { mechanism = "standard" return } else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 { mechanism = "extensible" } err = ErrUnsupportedEncryptMechanism return } // ECMA-376 Standard Encryption // standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption. func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opts *Options) ([]byte, error) { encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12]) block := encryptionInfoBuf[12 : 12+encryptionHeaderSize] header := StandardEncryptionHeader{ Flags: binary.LittleEndian.Uint32(block[:4]), SizeExtra: binary.LittleEndian.Uint32(block[4:8]), AlgID: binary.LittleEndian.Uint32(block[8:12]), AlgIDHash: binary.LittleEndian.Uint32(block[12:16]), KeySize: binary.LittleEndian.Uint32(block[16:20]), ProviderType: binary.LittleEndian.Uint32(block[20:24]), Reserved1: binary.LittleEndian.Uint32(block[24:28]), Reserved2: binary.LittleEndian.Uint32(block[28:32]), CspName: string(block[32:]), } block = encryptionInfoBuf[12+encryptionHeaderSize:] algIDMap := map[uint32]string{ 0x0000660E: "AES-128", 0x0000660F: "AES-192", 0x00006610: "AES-256", } algorithm := "AES" _, ok := algIDMap[header.AlgID] if !ok { algorithm = "RC4" } verifier := standardEncryptionVerifier(algorithm, block) secretKey, err := standardConvertPasswdToKey(header, verifier, opts) if err != nil { return nil, err } // decrypted data x := encryptedPackageBuf[8:] blob, err := aes.NewCipher(secretKey) if err != nil { return nil, err } decrypted := make([]byte, len(x)) size := 16 for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size { blob.Decrypt(decrypted[bs:be], x[bs:be]) } return decrypted, err } // standardEncryptionVerifier extract ECMA-376 standard encryption verifier. func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier { verifier := StandardEncryptionVerifier{ SaltSize: binary.LittleEndian.Uint32(blob[:4]), Salt: blob[4:20], EncryptedVerifier: blob[20:36], VerifierHashSize: binary.LittleEndian.Uint32(blob[36:40]), } switch algorithm { case "RC4": verifier.EncryptedVerifierHash = blob[40:60] case "AES": verifier.EncryptedVerifierHash = blob[40:72] } return verifier } // standardConvertPasswdToKey generate intermediate key from given password. func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opts *Options) ([]byte, error) { encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, err := encoder.Bytes([]byte(opts.Password)) if err != nil { return nil, err } key := hashing("sha1", verifier.Salt, passwordBuffer) for i := 0; i < iterCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing("sha1", iterator, key) } var block int hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4)) cbRequiredKeyLength := int(header.KeySize) / 8 cbHash := sha1.Size buf1 := bytes.Repeat([]byte{0x36}, 64) buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...) x1 := hashing("sha1", buf1) buf2 := bytes.Repeat([]byte{0x5c}, 64) buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...) x2 := hashing("sha1", buf2) x3 := append(x1, x2...) keyDerived := x3[:cbRequiredKeyLength] return keyDerived, err } // standardXORBytes perform XOR operations for two bytes slice. func standardXORBytes(a, b []byte) []byte { r := make([][2]byte, len(a)) for i, e := range a { r[i] = [2]byte{e, b[i]} } buf := make([]byte, len(a)) for p, q := range r { buf[p] = q[0] ^ q[1] } return buf } // encrypt provides a function to encrypt given value with AES cryptographic // algorithm. func (e *encryption) encrypt(input []byte) []byte { inputBytes := len(input) if pad := inputBytes % e.BlockSize; pad != 0 { inputBytes += e.BlockSize - pad } var output, chunk []byte encryptedChunk := make([]byte, e.BlockSize) for i := 0; i < inputBytes; i += e.BlockSize { if i+e.BlockSize <= len(input) { chunk = input[i : i+e.BlockSize] } else { chunk = input[i:] } chunk = append(chunk, make([]byte, e.BlockSize-len(chunk))...) c, _ := aes.NewCipher(e.EncryptedKeyValue) c.Encrypt(encryptedChunk, chunk) output = append(output, encryptedChunk...) } return output } // standardKeyEncryption encrypt convert the password to an encryption key. func (e *encryption) standardKeyEncryption(password string) ([]byte, error) { if countUTF16String(password) == 0 || countUTF16String(password) > MaxFieldLength { return nil, ErrPasswordLengthInvalid } var storage cfb storage.writeUint16(0x0003) storage.writeUint16(0x0002) storage.writeUint32(0x24) storage.writeUint32(0xA4) storage.writeUint32(0x24) storage.writeUint32(0x00) storage.writeUint32(0x660E) storage.writeUint32(0x8004) storage.writeUint32(0x80) storage.writeUint32(0x18) storage.writeUint64(0x00) providerName := "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" storage.writeStrings(providerName) storage.writeUint16(0x00) storage.writeUint32(0x10) keyDataSaltValue, _ := randomBytes(16) verifierHashInput, _ := randomBytes(16) e.SaltValue = keyDataSaltValue e.EncryptedKeyValue, _ = standardConvertPasswdToKey( StandardEncryptionHeader{KeySize: e.KeyBits}, StandardEncryptionVerifier{Salt: e.SaltValue}, &Options{Password: password}) verifierHashInputKey := hashing("sha1", verifierHashInput) e.EncryptedVerifierHashInput = e.encrypt(verifierHashInput) e.EncryptedVerifierHashValue = e.encrypt(verifierHashInputKey) storage.writeBytes(e.SaltValue) storage.writeBytes(e.EncryptedVerifierHashInput) storage.writeUint32(0x14) storage.writeBytes(e.EncryptedVerifierHashValue) storage.position = 0 return storage.stream, nil } // ECMA-376 Agile Encryption // agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption. // Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, // SHA384 and SHA512. func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opts *Options) (packageBuf []byte, err error) { var encryptionInfo Encryption if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil { return } // Convert the password into an encryption key. key, err := convertPasswdToKey(opts.Password, blockKey, encryptionInfo) if err != nil { return } // Use the key to decrypt the package key. encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) if err != nil { return } encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue) if err != nil { return } packageKey, _ := decrypt(key, saltValue, encryptedKeyValue) // Use the package key to decrypt the package. return decryptPackage(packageKey, encryptedPackageBuf, encryptionInfo) } // convertPasswdToKey convert the password into an encryption key. func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) { var b bytes.Buffer saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue) if err != nil { return } b.Write(saltValue) encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, err := encoder.Bytes([]byte(passwd)) if err != nil { return } b.Write(passwordBuffer) // Generate the initial hash. key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes()) // Now regenerate until spin count. for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing(encryption.KeyData.HashAlgorithm, iterator, key) } // Now generate the final hash. key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey) // Truncate or pad as needed to get to length of keyBits. keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8 if len(key) < keyBytes { tmp := make([]byte, 0x36) key = append(key, tmp...) } else if len(key) > keyBytes { key = key[:keyBytes] } return } // hashing data by specified hash algorithm. func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) { hashMap := map[string]hash.Hash{ "md4": md4.New(), "md5": md5.New(), "ripemd-160": ripemd160.New(), "sha1": sha1.New(), "sha256": sha256.New(), "sha384": sha512.New384(), "sha512": sha512.New(), } handler, ok := hashMap[strings.ToLower(hashAlgorithm)] if !ok { return key } for _, buf := range buffer { _, _ = handler.Write(buf) } key = handler.Sum(nil) return key } // createUInt32LEBuffer create buffer with little endian 32-bit unsigned // integer. func createUInt32LEBuffer(value int, bufferSize int) []byte { buf := make([]byte, bufferSize) binary.LittleEndian.PutUint32(buf, uint32(value)) return buf } // parseEncryptionInfo parse the encryption info XML into an object. func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) { err = xml.Unmarshal(encryptionInfo, &encryption) return } // decrypt provides a function to decrypt input by given cipher algorithm, // cipher chaining, key and initialization vector. func decrypt(key, iv, input []byte) (packageKey []byte, err error) { block, err := aes.NewCipher(key) if err != nil { return input, err } cipher.NewCBCDecrypter(block, iv).CryptBlocks(input, input) return input, nil } // decryptPackage decrypt package by given packageKey and encryption // info. func decryptPackage(packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) { encryptedKey, offset := encryption.KeyData, packageOffset var i, start, end int var iv, outputChunk []byte for end < len(input) { start = end end = start + packageEncryptionChunkSize if end > len(input) { end = len(input) } // Grab the next chunk var inputChunk []byte if (end + offset) < len(input) { inputChunk = input[start+offset : end+offset] } else { inputChunk = input[start+offset : end] } // Pad the chunk if it is not an integer multiple of the block size remainder := len(inputChunk) % encryptedKey.BlockSize if remainder != 0 { inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...) } // Create the initialization vector iv, err = createIV(i, encryption) if err != nil { return } // Decrypt the chunk and add it to the array outputChunk, err = decrypt(packageKey, iv, inputChunk) if err != nil { return } outputChunks = append(outputChunks, outputChunk...) i++ } return } // createIV create an initialization vector (IV). func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) { encryptedKey := encryption.KeyData // Create the block key from the current index var blockKeyBuf []byte if reflect.TypeOf(blockKey).Kind() == reflect.Int { blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4) } else { blockKeyBuf = blockKey.([]byte) } saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) if err != nil { return nil, err } // Create the initialization vector by hashing the salt with the block key. // Truncate or pad as needed to meet the block size. iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...)) if len(iv) < encryptedKey.BlockSize { tmp := make([]byte, 0x36) iv = append(iv, tmp...) } else if len(iv) > encryptedKey.BlockSize { iv = iv[:encryptedKey.BlockSize] } return iv, nil } // randomBytes returns securely generated random bytes. It will return an // error if the system's secure random number generator fails to function // correctly, in which case the caller should not continue. func randomBytes(n int) ([]byte, error) { b := make([]byte, n) _, err := rand.Read(b) return b, err } // ISO Write Protection Method // genISOPasswdHash implements the ISO password hashing algorithm by given // plaintext password, name of the cryptographic hash algorithm, salt value // and spin count. func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) { if countUTF16String(passwd) < 1 || countUTF16String(passwd) > MaxFieldLength { err = ErrPasswordLengthInvalid return } algorithmName, ok := map[string]string{ "MD4": "md4", "MD5": "md5", "SHA-1": "sha1", "SHA-256": "sha256", "SHA-384": "sha384", "SHA-512": "sha512", }[hashAlgorithm] if !ok { err = ErrUnsupportedHashAlgorithm return } var b bytes.Buffer s, _ := randomBytes(16) if salt != "" { if s, err = base64.StdEncoding.DecodeString(salt); err != nil { return } } b.Write(s) encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, _ := encoder.Bytes([]byte(passwd)) b.Write(passwordBuffer) // Generate the initial hash. key := hashing(algorithmName, b.Bytes()) // Now regenerate until spin count. for i := 0; i < spinCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing(algorithmName, key, iterator) } hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s) return } // Compound File Binary Implements // cfb structure is used for the compound file binary (CFB) file format writer. type cfb struct { stream []byte position int paths []string sectors []sector } // sector structure used for FAT, directory, miniFAT, and miniStream sectors. type sector struct { clsID, content []byte name string C, L, R, color, size, start, state, typeID int } // writeBytes write bytes in the stream by a given value with an offset. func (c *cfb) writeBytes(value []byte) { pos := c.position for i := 0; i < len(value); i++ { for j := len(c.stream); j <= i+pos; j++ { c.stream = append(c.stream, 0) } c.stream[i+pos] = value[i] } c.position = pos + len(value) } // writeUint16 write an uint16 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint16(value int) { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(value)) c.writeBytes(buf) } // writeUint32 write an uint32 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint32(value int) { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(value)) c.writeBytes(buf) } // writeUint64 write an uint64 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint64(value int) { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(value)) c.writeBytes(buf) } // writeStrings write strings in the stream by a given value with an offset. func (c *cfb) writeStrings(value string) { encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() buffer, err := encoder.Bytes([]byte(value)) if err != nil { return } c.writeBytes(buffer) } // put provides a function to add an entry to compound file by given entry name // and raw bytes. func (c *cfb) put(name string, content []byte) { path := c.paths[0] if len(path) <= len(name) && name[:len(path)] == path { path = name } else { if len(path) > 0 && string(path[len(path)-1]) != "/" { path += "/" } path = strings.ReplaceAll(path+name, "//", "/") } file := sector{name: path, typeID: 2, content: content, size: len(content)} c.sectors = append(c.sectors, file) c.paths = append(c.paths, path) } // compare provides a function to compare object path, each set of sibling // objects in one level of the containment hierarchy (all child objects under // a storage object) is represented as a red-black tree. The parent object of // this set of siblings will have a pointer to the top of this tree. func (c *cfb) compare(left, right string) int { L, R, i, j := strings.Split(left, "/"), strings.Split(right, "/"), 0, 0 for Z := int(math.Min(float64(len(L)), float64(len(R)))); i < Z; i++ { if j = len(L[i]) - len(R[i]); j != 0 { return j } if L[i] != R[i] { if L[i] < R[i] { return -1 } return 1 } } return len(L) - len(R) } // prepare provides a function to prepare object before write stream. func (c *cfb) prepare() { type object struct { path string sector sector } var objects []object for i := 0; i < len(c.paths); i++ { if c.sectors[i].typeID == 0 { continue } objects = append(objects, object{path: c.paths[i], sector: c.sectors[i]}) } sort.Slice(objects, func(i, j int) bool { return c.compare(objects[i].path, objects[j].path) == 0 }) c.paths, c.sectors = []string{}, []sector{} for i := 0; i < len(objects); i++ { c.paths = append(c.paths, objects[i].path) c.sectors = append(c.sectors, objects[i].sector) } for i := 0; i < len(objects); i++ { sector, path := &c.sectors[i], c.paths[i] sector.name, sector.color = filepath.Base(path), 1 sector.L, sector.R, sector.C = -1, -1, -1 sector.size, sector.start = len(sector.content), 0 if len(sector.clsID) == 0 { sector.clsID = headerCLSID } if i == 0 { sector.C = -1 if len(objects) > 1 { sector.C = 1 } sector.size, sector.typeID = 0, 5 } else { if len(c.paths) > i+1 && filepath.Dir(c.paths[i+1]) == filepath.Dir(path) { sector.R = i + 1 } sector.typeID = 2 } } } // locate provides a function to locate sectors location and size of the // compound file. func (c *cfb) locate() []int { var miniStreamSectorSize, FATSectorSize int for i := 0; i < len(c.sectors); i++ { sector := c.sectors[i] if len(sector.content) == 0 { continue } size := len(sector.content) if size > 0 { if size < 0x1000 { miniStreamSectorSize += (size + 0x3F) >> 6 } else { FATSectorSize += (size + 0x01FF) >> 9 } } } directorySectors := (len(c.paths) + 3) >> 2 miniStreamSectors := (miniStreamSectorSize + 7) >> 3 miniFATSectors := (miniStreamSectorSize + 0x7F) >> 7 sectors := miniStreamSectors + FATSectorSize + directorySectors + miniFATSectors FATSectors := (sectors + 0x7F) >> 7 DIFATSectors := 0 if FATSectors > 109 { DIFATSectors = int(math.Ceil((float64(FATSectors) - 109) / 0x7F)) } for ((sectors + FATSectors + DIFATSectors + 0x7F) >> 7) > FATSectors { FATSectors++ if FATSectors <= 109 { DIFATSectors = 0 } else { DIFATSectors = int(math.Ceil((float64(FATSectors) - 109) / 0x7F)) } } location := []int{1, DIFATSectors, FATSectors, miniFATSectors, directorySectors, FATSectorSize, miniStreamSectorSize, 0} c.sectors[0].size = miniStreamSectorSize << 6 c.sectors[0].start = location[0] + location[1] + location[2] + location[3] + location[4] + location[5] location[7] = c.sectors[0].start + ((location[6] + 7) >> 3) return location } // writeMSAT provides a function to write compound file master sector allocation // table. func (c *cfb) writeMSAT(location []int) { var i, offset int for i = 0; i < 109; i++ { if i < location[2] { c.writeUint32(location[1] + i) } else { c.writeUint32(-1) } } if location[1] != 0 { for offset = 0; offset < location[1]; offset++ { for ; i < 236+offset*127; i++ { if i < location[2] { c.writeUint32(location[1] + i) } else { c.writeUint32(-1) } } if offset == location[1]-1 { c.writeUint32(endOfChain) } else { c.writeUint32(offset + 1) } } } } // writeDirectoryEntry provides a function to write compound file directory // entries. The directory entry array is an array of directory entries that // are grouped into a directory sector. Each storage object or stream object // within a compound file is represented by a single directory entry. The // space for the directory sectors that are holding the array is allocated // from the FAT. func (c *cfb) writeDirectoryEntry(location []int) { var sector sector var j, sectorSize int for i := 0; i < location[4]<<2; i++ { var path string if i < len(c.paths) { path = c.paths[i] } if i >= len(c.paths) || len(path) == 0 { for j = 0; j < 17; j++ { c.writeUint32(0) } for j = 0; j < 3; j++ { c.writeUint32(-1) } for j = 0; j < 12; j++ { c.writeUint32(0) } continue } sector = c.sectors[i] if i == 0 { if sector.size > 0 { sector.start = sector.start - 1 } else { sector.start = endOfChain } } name := sector.name sectorSize = 2 * (len(name) + 1) c.writeStrings(name) c.position += 64 - 2*(len(name)) c.writeUint16(sectorSize) c.writeBytes([]byte(string(rune(sector.typeID)))) c.writeBytes([]byte(string(rune(sector.color)))) c.writeUint32(sector.L) c.writeUint32(sector.R) c.writeUint32(sector.C) if len(sector.clsID) == 0 { for j = 0; j < 4; j++ { c.writeUint32(0) } } else { c.writeBytes(sector.clsID) } c.writeUint32(sector.state) c.writeUint32(0) c.writeUint32(0) c.writeUint32(0) c.writeUint32(0) c.writeUint32(sector.start) c.writeUint32(sector.size) c.writeUint32(0) } } // writeSectorChains provides a function to write compound file sector chains. func (c *cfb) writeSectorChains(location []int) sector { var i, j, offset, sectorSize int writeSectorChain := func(head, offset int) int { for offset += head; i < offset-1; i++ { c.writeUint32(i + 1) } if head != 0 { i++ c.writeUint32(endOfChain) } return offset } for offset += location[1]; i < offset; i++ { c.writeUint32(difSect) } for offset += location[2]; i < offset; i++ { c.writeUint32(fatSect) } offset = writeSectorChain(location[3], offset) offset = writeSectorChain(location[4], offset) sector := c.sectors[0] for ; j < len(c.sectors); j++ { if sector = c.sectors[j]; len(sector.content) == 0 { continue } if sectorSize = len(sector.content); sectorSize < 0x1000 { continue } c.sectors[j].start = offset offset = writeSectorChain((sectorSize+0x01FF)>>9, offset) } writeSectorChain((location[6]+7)>>3, offset) for c.position&0x1FF != 0 { c.writeUint32(endOfChain) } i, offset = 0, 0 for j = 0; j < len(c.sectors); j++ { if sector = c.sectors[j]; len(sector.content) == 0 { continue } if sectorSize = len(sector.content); sectorSize == 0 || sectorSize >= 0x1000 { continue } sector.start = offset offset = writeSectorChain((sectorSize+0x3F)>>6, offset) } for c.position&0x1FF != 0 { c.writeUint32(endOfChain) } return sector } // write provides a function to create compound file package stream. func (c *cfb) write() []byte { c.prepare() location := c.locate() c.stream = make([]byte, location[7]<<9) var i, j int for i = 0; i < 8; i++ { c.writeBytes([]byte{oleIdentifier[i]}) } c.writeBytes(make([]byte, 16)) c.writeUint16(0x003E) c.writeUint16(0x0003) c.writeUint16(0xFFFE) c.writeUint16(0x0009) c.writeUint16(0x0006) c.writeBytes(make([]byte, 10)) c.writeUint32(location[2]) c.writeUint32(location[0] + location[1] + location[2] + location[3] - 1) c.writeUint32(0) c.writeUint32(1 << 12) if location[3] != 0 { c.writeUint32(location[0] + location[1] + location[2] - 1) } else { c.writeUint32(endOfChain) } c.writeUint32(location[3]) if location[1] != 0 { c.writeUint32(location[0] - 1) } else { c.writeUint32(endOfChain) } c.writeUint32(location[1]) c.writeMSAT(location) sector := c.writeSectorChains(location) c.writeDirectoryEntry(location) for i = 1; i < len(c.sectors); i++ { sector = c.sectors[i] if sector.size >= 0x1000 { c.position = (sector.start + 1) << 9 for j = 0; j < sector.size; j++ { c.writeBytes([]byte{sector.content[j]}) } for ; j&0x1FF != 0; j++ { c.writeBytes([]byte{0}) } } } for i = 1; i < len(c.sectors); i++ { sector = c.sectors[i] if sector.size > 0 && sector.size < 0x1000 { for j = 0; j < sector.size; j++ { c.writeBytes([]byte{sector.content[j]}) } for ; j&0x3F != 0; j++ { c.writeBytes([]byte{0}) } } } for c.position < len(c.stream) { c.writeBytes([]byte{0}) } return c.stream } ================================================ FILE: crypt_test.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/base64" "encoding/binary" "os" "path/filepath" "strings" "testing" "github.com/richardlehane/mscfb" "github.com/stretchr/testify/assert" ) func TestEncrypt(t *testing.T) { // Test decrypt spreadsheet with incorrect password _, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "passwd"}) assert.EqualError(t, err, ErrWorkbookPassword.Error()) // Test decrypt spreadsheet with password f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"}) assert.NoError(t, err) cell, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "SECRET", cell) assert.NoError(t, f.Close()) // Test decrypt spreadsheet with unsupported encrypt mechanism raw, err := os.ReadFile(filepath.Join("test", "encryptAES.xlsx")) assert.NoError(t, err) raw[2050] = 3 _, err = Decrypt(raw, &Options{Password: "password"}) assert.Equal(t, ErrUnsupportedEncryptMechanism, err) // Test encrypt spreadsheet with invalid password assert.EqualError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: strings.Repeat("*", MaxFieldLength+1)}), ErrPasswordLengthInvalid.Error()) // Test encrypt spreadsheet with new password assert.NoError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"})) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"}) assert.NoError(t, err) cell, err = f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "SECRET", cell) // Test remove password by save workbook with options assert.NoError(t, f.Save(Options{Password: ""})) assert.NoError(t, f.Close()) doc, err := mscfb.New(bytes.NewReader(raw)) assert.NoError(t, err) encryptionInfoBuf, encryptedPackageBuf, err := extractPart(doc) assert.NoError(t, err) binary.LittleEndian.PutUint64(encryptionInfoBuf[20:32], uint64(0)) _, err = standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, &Options{Password: "password"}) assert.NoError(t, err) _, err = decrypt(nil, nil, nil) assert.EqualError(t, err, "crypto/aes: invalid key size 0") _, err = agileDecrypt(encryptionInfoBuf, MacintoshCyrillicCharset, &Options{Password: "password"}) assert.EqualError(t, err, "XML syntax error on line 1: invalid character entity &0 (no semicolon)") _, err = convertPasswdToKey("password", nil, Encryption{ KeyEncryptors: KeyEncryptors{KeyEncryptor: []KeyEncryptor{ {EncryptedKey: EncryptedKey{KeyData: KeyData{SaltValue: "=="}}}, }}, }) assert.EqualError(t, err, "illegal base64 data at input byte 0") _, err = createIV([]byte{0}, Encryption{KeyData: KeyData{SaltValue: "=="}}) assert.EqualError(t, err, "illegal base64 data at input byte 0") // Test error handling for EncryptionInfo parse failure compoundFile := &cfb{ paths: []string{"Root Entry/"}, sectors: []sector{{name: "Root Entry", typeID: 5}}, } compoundFile.put("EncryptionInfo", []byte{}) _, err = OpenReader(bytes.NewReader(compoundFile.write())) assert.Equal(t, ErrWorkbookFileFormat, err) // Test error handling for EncryptedPackage parse failure compoundFile = &cfb{ paths: []string{"Root Entry/"}, sectors: []sector{{name: "Root Entry", typeID: 5}}, } encryptionInfo := make([]byte, 100) binary.LittleEndian.PutUint16(encryptionInfo[:2], 4) binary.LittleEndian.PutUint16(encryptionInfo[2:4], 4) compoundFile.put("EncryptionInfo", encryptionInfo) compoundFile.put("EncryptedPackage", []byte{}) _, err = OpenReader(bytes.NewReader(compoundFile.write())) assert.Equal(t, ErrWorkbookFileFormat, err) // Test createIV when iv length is less than block size _, err = createIV(0, Encryption{ KeyData: KeyData{ HashAlgorithm: "md5", BlockSize: 32, SaltValue: base64.StdEncoding.EncodeToString([]byte("")), }, }) assert.NoError(t, err) // Test decryptPackage error with padding input := make([]byte, 18) binary.LittleEndian.PutUint64(input[:8], 10) for i := 8; i < 18; i++ { input[i] = byte(i) } _, err = decryptPackage(make([]byte, 32), input, Encryption{ KeyData: KeyData{ HashAlgorithm: "sha256", BlockSize: 16, SaltValue: base64.StdEncoding.EncodeToString([]byte("")), }, }) assert.NoError(t, err) // Test IV creation error with invalid salt input = make([]byte, 4104) binary.LittleEndian.PutUint64(input[:8], 4096) _, err = decryptPackage(make([]byte, 32), input, Encryption{ KeyData: KeyData{ HashAlgorithm: "sha256", BlockSize: 16, SaltValue: "==", }, }) assert.Error(t, err) // Test decrypt error with invalid key _, err = decryptPackage([]byte{}, input, Encryption{ KeyData: KeyData{ HashAlgorithm: "sha256", BlockSize: 16, SaltValue: base64.StdEncoding.EncodeToString([]byte("")), }, }) assert.Error(t, err) // Test put with path that is a prefix of name compoundFile = &cfb{ paths: []string{"Root Entry/"}, sectors: []sector{{name: "Root Entry", typeID: 5}}, } compoundFile.put("Root Entry/Test", []byte("")) compoundFile = &cfb{ paths: []string{"Root"}, sectors: []sector{{name: "Root", typeID: 5}}, } compoundFile.put("Test", []byte("")) // Test compare function with different scenarios compoundFile = &cfb{} assert.Equal(t, -1, compoundFile.compare("Root/A", "Root/B")) assert.Equal(t, 1, compoundFile.compare("Root/B", "Root/A")) assert.Equal(t, -1, compoundFile.compare("Root", "Root/Child")) // Test prepare with typeID == 0 sector compoundFile = &cfb{ paths: []string{"Root Entry/", "Skip/", "Valid/"}, sectors: []sector{ {name: "Root Entry", typeID: 5}, {name: "Skip", typeID: 0}, {name: "Valid", typeID: 2}, }, } compoundFile.prepare() // Test locate with FATSectors incrementing but staying <= 109 compoundFile = &cfb{ paths: []string{"Root Entry/"}, sectors: []sector{{name: "Root Entry", typeID: 5, content: []byte{}}}, } numFiles := 1533 for i := range numFiles { name := strings.Repeat("F", i%50+1) compoundFile.paths = append(compoundFile.paths, name+"/") compoundFile.sectors = append(compoundFile.sectors, sector{ name: name, typeID: 2, content: make([]byte, 4096), }) } compoundFile.locate() // Test writeDirectoryEntry with empty clsID compoundFile = &cfb{ paths: []string{"Root Entry/", "File1/"}, sectors: []sector{ {name: "Root Entry", typeID: 5, content: []byte{}, clsID: headerCLSID}, {name: "File1", typeID: 2, content: []byte("test"), clsID: []byte{}}, }, } compoundFile.stream = make([]byte, 10000) compoundFile.writeDirectoryEntry([]int{1, 0, 1, 0, 1, 0, 0, 0}) } func TestEncryptionMechanism(t *testing.T) { mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0}) assert.Equal(t, mechanism, "extensible") assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error()) _, err = encryptionMechanism([]byte{}) assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error()) } func TestHashing(t *testing.T) { assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []byte(nil)) } func TestGenISOPasswdHash(t *testing.T) { for hashAlgorithm, expected := range map[string][]string{ "MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="}, "MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="}, "SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="}, "SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="}, "SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="}, "SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="}, } { hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount)) assert.NoError(t, err) assert.Equal(t, expected[0], hashValue) assert.Equal(t, expected[1], saltValue) } } ================================================ FILE: datavalidation.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "fmt" "io" "math" "slices" "strings" ) // DataValidationType defined the type of data validation. type DataValidationType byte // Data validation types. const ( _ DataValidationType = iota DataValidationTypeNone DataValidationTypeCustom DataValidationTypeDate DataValidationTypeDecimal DataValidationTypeList DataValidationTypeTextLength DataValidationTypeTime DataValidationTypeWhole ) // DataValidationErrorStyle defined the style of data validation error alert. type DataValidationErrorStyle byte // Data validation error styles. const ( _ DataValidationErrorStyle = iota DataValidationErrorStyleStop DataValidationErrorStyleWarning DataValidationErrorStyleInformation ) // Data validation error styles. const ( styleStop = "stop" styleWarning = "warning" styleInformation = "information" ) // DataValidationOperator operator enum. type DataValidationOperator byte // Data validation operators. const ( _ DataValidationOperator = iota DataValidationOperatorBetween DataValidationOperatorEqual DataValidationOperatorGreaterThan DataValidationOperatorGreaterThanOrEqual DataValidationOperatorLessThan DataValidationOperatorLessThanOrEqual DataValidationOperatorNotBetween DataValidationOperatorNotEqual ) var ( // formulaEscaper mimics the Excel escaping rules for data validation, // which converts `"` to `""` instead of `"`. formulaEscaper = strings.NewReplacer( `&`, `&`, `<`, `<`, `>`, `>`, ) formulaUnescaper = strings.NewReplacer( `&`, `&`, `<`, `<`, `>`, `>`, ) // dataValidationTypeMap defined supported data validation types. dataValidationTypeMap = map[DataValidationType]string{ DataValidationTypeNone: "none", DataValidationTypeCustom: "custom", DataValidationTypeDate: "date", DataValidationTypeDecimal: "decimal", DataValidationTypeList: "list", DataValidationTypeTextLength: "textLength", DataValidationTypeTime: "time", DataValidationTypeWhole: "whole", } // dataValidationOperatorMap defined supported data validation operators. dataValidationOperatorMap = map[DataValidationOperator]string{ DataValidationOperatorBetween: "between", DataValidationOperatorEqual: "equal", DataValidationOperatorGreaterThan: "greaterThan", DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual", DataValidationOperatorLessThan: "lessThan", DataValidationOperatorLessThanOrEqual: "lessThanOrEqual", DataValidationOperatorNotBetween: "notBetween", DataValidationOperatorNotEqual: "notEqual", } ) // NewDataValidation return data validation struct. func NewDataValidation(allowBlank bool) *DataValidation { return &DataValidation{ AllowBlank: allowBlank, ShowErrorMessage: false, ShowInputMessage: false, } } // SetError set error notice. func (dv *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) { dv.Error = &msg dv.ErrorTitle = &title strStyle := styleStop switch style { case DataValidationErrorStyleStop: strStyle = styleStop case DataValidationErrorStyleWarning: strStyle = styleWarning case DataValidationErrorStyleInformation: strStyle = styleInformation } dv.ShowErrorMessage = true dv.ErrorStyle = &strStyle } // SetInput set prompt notice. func (dv *DataValidation) SetInput(title, msg string) { dv.ShowInputMessage = true dv.PromptTitle = &title dv.Prompt = &msg } // SetDropList data validation list. If you type the items into the data // validation dialog box (a delimited list), the limit is 255 characters, // including the separators. If your data validation list source formula is // over the maximum length limit, please set the allowed values in the // worksheet cells, and use the SetSqrefDropList function to set the reference // for their cells. func (dv *DataValidation) SetDropList(keys []string) error { formula := strings.Join(keys, ",") if MaxFieldLength < countUTF16String(formula) { return ErrDataValidationFormulaLength } dv.Type = dataValidationTypeMap[DataValidationTypeList] if strings.HasPrefix(formula, "=") { dv.Formula1 = formulaEscaper.Replace(formula) return nil } dv.Formula1 = fmt.Sprintf(`"%s"`, strings.NewReplacer(`"`, `""`).Replace(formulaEscaper.Replace(formula))) return nil } // SetRange provides function to set data validation range in drop list, only // accepts int, float64, string or []string data type formula argument. func (dv *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error { genFormula := func(val interface{}) (string, error) { var formula string switch v := val.(type) { case int: formula = fmt.Sprintf("%d", v) case float64: if math.Abs(v) > math.MaxFloat32 { return formula, ErrDataValidationRange } formula = fmt.Sprintf("%.17g", v) case string: formula = v default: return formula, ErrParameterInvalid } return formula, nil } formula1, err := genFormula(f1) if err != nil { return err } formula2, err := genFormula(f2) if err != nil { return err } dv.Formula1, dv.Formula2 = formula1, formula2 dv.Type = dataValidationTypeMap[t] dv.Operator = dataValidationOperatorMap[o] return err } // SetSqrefDropList provides set data validation on a range with source // reference range of the worksheet by given data validation object and // worksheet name. The data validation object can be created by // NewDataValidation function. There are limits to the number of items that // will show in a data validation drop down list: The list can show up to show // 32768 items from a list on the worksheet. If you need more items than that, // you could create a dependent drop down list, broken down by category. For // example, set data validation on Sheet1!A7:B8 with validation criteria source // Sheet1!E1:E3 settings, create in-cell dropdown by allowing list source: // // dv := excelize.NewDataValidation(true) // dv.Sqref = "A7:B8" // dv.SetSqrefDropList("$E$1:$E$3") // err := f.AddDataValidation("Sheet1", dv) func (dv *DataValidation) SetSqrefDropList(sqref string) { dv.Formula1 = sqref dv.Type = dataValidationTypeMap[DataValidationTypeList] } // SetSqref provides function to set data validation range in drop list. func (dv *DataValidation) SetSqref(sqref string) { if dv.Sqref == "" { dv.Sqref = sqref return } dv.Sqref = fmt.Sprintf("%s %s", dv.Sqref, sqref) } // AddDataValidation provides set data validation on a range of the worksheet // by given data validation object and worksheet name. This function is // concurrency safe. The data validation object can be created by // NewDataValidation function. // // Example 1, set data validation on Sheet1!A1:B2 with validation criteria // settings, show error alert after invalid data is entered with "Stop" style // and custom title "error body": // // dv := excelize.NewDataValidation(true) // dv.Sqref = "A1:B2" // dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween) // dv.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body") // err := f.AddDataValidation("Sheet1", dv) // // Example 2, set data validation on Sheet1!A3:B4 with validation criteria // settings, and show input message when cell is selected: // // dv = excelize.NewDataValidation(true) // dv.Sqref = "A3:B4" // dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan) // dv.SetInput("input title", "input body") // err = f.AddDataValidation("Sheet1", dv) // // Example 3, set data validation on Sheet1!A5:B6 with validation criteria // settings, create in-cell dropdown by allowing list source: // // dv = excelize.NewDataValidation(true) // dv.Sqref = "A5:B6" // dv.SetDropList([]string{"1", "2", "3"}) // err = f.AddDataValidation("Sheet1", dv) func (f *File) AddDataValidation(sheet string, dv *DataValidation) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.mu.Lock() defer ws.mu.Unlock() if nil == ws.DataValidations { ws.DataValidations = new(xlsxDataValidations) } dataValidation := &xlsxDataValidation{ AllowBlank: dv.AllowBlank, Error: dv.Error, ErrorStyle: dv.ErrorStyle, ErrorTitle: dv.ErrorTitle, Operator: dv.Operator, Prompt: dv.Prompt, PromptTitle: dv.PromptTitle, ShowDropDown: dv.ShowDropDown, ShowErrorMessage: dv.ShowErrorMessage, ShowInputMessage: dv.ShowInputMessage, Sqref: dv.Sqref, Type: dv.Type, } if dv.Formula1 != "" { dataValidation.Formula1 = &xlsxInnerXML{Content: dv.Formula1} } if dv.Formula2 != "" { dataValidation.Formula2 = &xlsxInnerXML{Content: dv.Formula2} } ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dataValidation) ws.DataValidations.Count = len(ws.DataValidations.DataValidation) return err } // GetDataValidations returns data validations list by given worksheet name. func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) { ws, err := f.workSheetReader(sheet) if err != nil { return nil, err } var ( dataValidations []*DataValidation decodeExtLst = new(decodeExtLst) decodeDataValidations *xlsxDataValidations ext *xlsxExt ) if ws.DataValidations != nil { dataValidations = append(dataValidations, getDataValidations(ws.DataValidations)...) } if ws.ExtLst != nil { if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return dataValidations, err } for _, ext = range decodeExtLst.Ext { if ext.URI == ExtURIDataValidations { decodeDataValidations = new(xlsxDataValidations) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations) dataValidations = append(dataValidations, getDataValidations(decodeDataValidations)...) } } } return dataValidations, err } // getDataValidations returns data validations list by given worksheet data // validations. func getDataValidations(dvs *xlsxDataValidations) []*DataValidation { if dvs == nil { return nil } var dataValidations []*DataValidation for _, dv := range dvs.DataValidation { if dv == nil { continue } dataValidation := &DataValidation{ AllowBlank: dv.AllowBlank, Error: dv.Error, ErrorStyle: dv.ErrorStyle, ErrorTitle: dv.ErrorTitle, Operator: dv.Operator, Prompt: dv.Prompt, PromptTitle: dv.PromptTitle, ShowDropDown: dv.ShowDropDown, ShowErrorMessage: dv.ShowErrorMessage, ShowInputMessage: dv.ShowInputMessage, Sqref: dv.Sqref, Type: dv.Type, } if dv.Formula1 != nil { dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content) } if dv.Formula2 != nil { dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content) } if dv.XMSqref != "" { dataValidation.Sqref = dv.XMSqref dataValidation.Formula1 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula1, ""), "") dataValidation.Formula2 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula2, ""), "") } dataValidations = append(dataValidations, dataValidation) } return dataValidations } // DeleteDataValidation delete data validation by given worksheet name and // reference sequence. This function is concurrency safe. All data validations // in the worksheet will be deleted if not specify reference sequence parameter. // // Example 1, delete data validation on Sheet1!A1:B2: // // err := f.DeleteDataValidation("Sheet1", "A1:B2") // // Example 2, delete data validations on Sheet1 with multiple cell ranges // A1:B2 and C1:C3 with reference sequence slice: // // err := f.DeleteDataValidation("Sheet1", []string{"A1:B2", "C1:C3"}...) // // Example 3, delete data validations on Sheet1 with multiple cell ranges // A1:B2 and C1:C3 with blank separated reference sequence string, the result // same as example 2: // // err := f.DeleteDataValidation("Sheet1", "A1:B2 C1:C3") // // Example 4, delete all data validations on Sheet1: // // err := f.DeleteDataValidation("Sheet1") func (f *File) DeleteDataValidation(sheet string, sqref ...string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.mu.Lock() defer ws.mu.Unlock() if ws.DataValidations == nil && ws.ExtLst == nil { return nil } if sqref == nil { ws.DataValidations = nil return nil } delCells, err := flatSqref(strings.Join(sqref, " ")) if err != nil { return err } if ws.DataValidations != nil { if err = f.deleteDataValidation(ws, delCells); err != nil { return err } } if ws.ExtLst != nil { return f.deleteX14DataValidation(ws, delCells) } return nil } // deleteDataValidation deletes data validation by given worksheet and cell // reference list. func (f *File) deleteDataValidation(ws *xlsxWorksheet, delCells map[int][][]int) error { dv := ws.DataValidations var err error for i := 0; i < len(dv.DataValidation); i++ { if dv.DataValidation[i].Sqref, err = deleteCellsFromSqref(dv.DataValidation[i].Sqref, delCells); err != nil { return err } if len(dv.DataValidation[i].Sqref) == 0 { dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...) i-- } } dv.Count = len(dv.DataValidation) if dv.Count == 0 { ws.DataValidations = nil } return nil } // deleteX14DataValidation deletes data validation in the extLst element by // given worksheet and cell reference list. func (f *File) deleteX14DataValidation(ws *xlsxWorksheet, delCells map[int][][]int) error { var ( err error decodeExtLst = new(decodeExtLst) decodeDataValidations *xlsxDataValidations x14DataValidations *xlsxX14DataValidations ) if err := f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for i, ext := range decodeExtLst.Ext { if ext.URI == ExtURIDataValidations { decodeDataValidations = new(xlsxDataValidations) x14DataValidations = new(xlsxX14DataValidations) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations) x14DataValidations.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value x14DataValidations.DisablePrompts = decodeDataValidations.DisablePrompts x14DataValidations.XWindow = decodeDataValidations.XWindow x14DataValidations.YWindow = decodeDataValidations.YWindow for _, dv := range decodeDataValidations.DataValidation { if dv.XMSqref, err = deleteCellsFromSqref(dv.XMSqref, delCells); err != nil { return err } if len(dv.XMSqref) == 0 { continue } x14DataValidations.DataValidation = append(x14DataValidations.DataValidation, &xlsxX14DataValidation{ AllowBlank: dv.AllowBlank, Error: dv.Error, ErrorStyle: dv.ErrorStyle, ErrorTitle: dv.ErrorTitle, Operator: dv.Operator, Prompt: dv.Prompt, PromptTitle: dv.PromptTitle, ShowDropDown: dv.ShowDropDown, ShowErrorMessage: dv.ShowErrorMessage, ShowInputMessage: dv.ShowInputMessage, Sqref: dv.Sqref, XMSqref: dv.XMSqref, Type: dv.Type, Formula1: dv.Formula1, Formula2: dv.Formula2, }) } x14DataValidations.Count = len(x14DataValidations.DataValidation) x14DataValidationsBytes, _ := xml.Marshal(x14DataValidations) decodeExtLst.Ext[i] = &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}}, URI: ExtURIDataValidations, Content: string(x14DataValidationsBytes), } if x14DataValidations.Count == 0 { decodeExtLst.Ext = slices.Delete(decodeExtLst.Ext, i, i+1) } } } extLstBytes, err := xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } // squashSqref generates cell reference sequence by given cells coordinates list. func squashSqref(cells [][]int) []string { if len(cells) == 1 { cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1]) return []string{cell} } else if len(cells) == 0 { return []string{} } var refs []string l, r := 0, 0 for i := 1; i < len(cells); i++ { if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 { ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...)) if l == r { ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) } refs = append(refs, ref) l, r = i, i } else { r++ } } ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...)) if l == r { ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) } return append(refs, ref) } // deleteCellsFromSqref deletes cells from given sqref by delCells map and // returns the updated sqref. func deleteCellsFromSqref(sqref string, delCells map[int][][]int) (string, error) { var ( ref string applySqref []string ) colCells, err := flatSqref(sqref) if err != nil { return ref, err } for col, cells := range delCells { for _, cell := range cells { idx := inCoordinates(colCells[col], cell) if idx != -1 { colCells[col] = append(colCells[col][:idx], colCells[col][idx+1:]...) } } } for _, col := range colCells { applySqref = append(applySqref, squashSqref(col)...) } return strings.Join(applySqref, " "), err } // isFormulaDataValidation returns whether the data validation rule is a formula. func (dv *xlsxInnerXML) isFormula() bool { return dv != nil && (!strings.HasPrefix(dv.Content, """) || !strings.HasSuffix(dv.Content, """)) } // unescapeDataValidationFormula returns unescaped data validation formula. func unescapeDataValidationFormula(val string) string { if strings.HasPrefix(val, "\"") { // Text detection return strings.NewReplacer(`""`, `"`).Replace(formulaUnescaper.Replace(val)) } return formulaUnescaper.Replace(val) } ================================================ FILE: datavalidation_test.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "fmt" "math" "path/filepath" "strings" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestDataValidation(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidation.xlsx") f := NewFile() dv := NewDataValidation(true) dv.Sqref = "A1:B2" assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetError(DataValidationErrorStyleStop, "error title", "error body") dv.SetError(DataValidationErrorStyleWarning, "error title", "error body") dv.SetError(DataValidationErrorStyleInformation, "error title", "error body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dataValidations, 1) assert.NoError(t, f.SaveAs(resultFile)) dv = NewDataValidation(true) dv.Sqref = "A3:B4" assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) dv.SetInput("input title", "input body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dataValidations, 2) assert.NoError(t, f.SaveAs(resultFile)) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet2", "A2", &[]interface{}{"B2", 1})) assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3})) dv = NewDataValidation(true) dv.Sqref = "A1:B1" assert.NoError(t, dv.SetRange("INDIRECT($A$2)", "INDIRECT($A$3)", DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetError(DataValidationErrorStyleStop, "error title", "error body") assert.NoError(t, f.AddDataValidation("Sheet2", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dataValidations, 2) dataValidations, err = f.GetDataValidations("Sheet2") assert.NoError(t, err) assert.Len(t, dataValidations, 1) dv = NewDataValidation(true) dv.Sqref = "A5:B6" for _, listValid := range [][]string{ {"1", "2", "3"}, {"=A1"}, {strings.Repeat("&", MaxFieldLength)}, {strings.Repeat("\u4E00", MaxFieldLength)}, {strings.Repeat("\U0001F600", 100), strings.Repeat("\u4E01", 50), "<&>"}, {`A<`, `B>`, `C"`, "D\t", `E'`, `F`}, } { dv.Formula1 = "" assert.NoError(t, dv.SetDropList(listValid), "SetDropList failed for valid input %v", listValid) assert.NotEmpty(t, dv.Formula1, "Formula1 should not be empty for valid input %v", listValid) } assert.Equal(t, `"A<,B>,C"",D ,E',F"`, dv.Formula1) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dataValidations, 3) // Test get data validation on no exists worksheet _, err = f.GetDataValidations("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get data validation with invalid sheet name _, err = f.GetDataValidations("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(resultFile)) // Test get data validation on a worksheet without data validation settings f = NewFile() dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, []*DataValidation(nil), dataValidations) // Test get data validations which storage in the extension lists f = NewFile() ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`Sheet1!$B$1:$B$5A7:B8`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)} dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, []*DataValidation{ { AllowBlank: true, Type: "list", Formula1: "Sheet1!$B$1:$B$5", Sqref: "A7:B8", }, }, dataValidations) // Test get data validations with invalid extension list characters ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)} _, err = f.GetDataValidations("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: element closed by ") // Test get validations without validations assert.Nil(t, getDataValidations(nil)) assert.Nil(t, getDataValidations(&xlsxDataValidations{DataValidation: []*xlsxDataValidation{nil}})) } func TestDataValidationError(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidationError.xlsx") f := NewFile() assert.NoError(t, f.SetCellStr("Sheet1", "E1", "E1")) assert.NoError(t, f.SetCellStr("Sheet1", "E2", "E2")) assert.NoError(t, f.SetCellStr("Sheet1", "E3", "E3")) dv := NewDataValidation(true) dv.SetSqref("A7:B8") dv.SetSqref("A7:B8") dv.SetSqrefDropList("$E$1:$E$3") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dv = NewDataValidation(true) err := dv.SetDropList(make([]string, 258)) if dv.Formula1 != "" { t.Errorf("data validation error. Formula1 must be empty!") return } assert.EqualError(t, err, ErrDataValidationFormulaLength.Error()) assert.EqualError(t, dv.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) assert.EqualError(t, dv.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) dv.SetSqref("A9:B10") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) // Test width invalid data validation formula prevFormula1 := dv.Formula1 for _, keys := range [][]string{ make([]string, 257), {strings.Repeat("s", 256)}, {strings.Repeat("\u4E00", 256)}, {strings.Repeat("\U0001F600", 128)}, {strings.Repeat("\U0001F600", 127), "s"}, } { err = dv.SetDropList(keys) assert.Equal(t, prevFormula1, dv.Formula1, "Formula1 should be unchanged for invalid input %v", keys) assert.EqualError(t, err, ErrDataValidationFormulaLength.Error()) } assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, dv.SetRange( -math.MaxFloat32, math.MaxFloat32, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) assert.EqualError(t, dv.SetRange( -math.MaxFloat64, math.MaxFloat32, DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error()) assert.EqualError(t, dv.SetRange( math.SmallestNonzeroFloat64, math.MaxFloat64, DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error()) assert.NoError(t, f.SaveAs(resultFile)) // Test add data validation on no exists worksheet f = NewFile() assert.EqualError(t, f.AddDataValidation("SheetN", nil), "sheet SheetN does not exist") // Test add data validation with invalid sheet name f = NewFile() assert.EqualError(t, f.AddDataValidation("Sheet:1", nil), ErrSheetNameInvalid.Error()) } func TestDeleteDataValidation(t *testing.T) { f := NewFile() assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) dv := NewDataValidation(true) dv.Sqref = "A1:B2" assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetInput("input title", "input body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) dv.Sqref = "A1" assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1")) assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1")) dv.Sqref = "C2:C5" assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "C4")) dv = NewDataValidation(true) dv.Sqref = "D2:D2 D3 D4" assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) dv.SetInput("input title", "input body") assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "D3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteDataValidation.xlsx"))) dv.Sqref = "A" assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1:A" assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:B2"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test delete data validation on no exists worksheet assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN does not exist") // Test delete all data validation with invalid sheet name assert.EqualError(t, f.DeleteDataValidation("Sheet:1"), ErrSheetNameInvalid.Error()) // Test delete all data validations in the worksheet assert.NoError(t, f.DeleteDataValidation("Sheet1")) assert.Nil(t, ws.(*xlsxWorksheet).DataValidations) t.Run("with_data_validation_in_extLst", func(t *testing.T) { f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil, `A1:A2Sheet1!$A$2:$A$4B1:B2Sheet1!$B$2:$B$3`, NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value, ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value)) f.checked = sync.Map{} assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:A2")) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dvs, 1) assert.Equal(t, "B1:B2", dvs[0].Sqref) assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1:B2")) dvs, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Empty(t, dvs) }) t.Run("with_invalid_data_validation_in_extLst", func(t *testing.T) { f := NewFile() assert.EqualError(t, f.deleteX14DataValidation(&xlsxWorksheet{ ExtLst: &xlsxExtLst{Ext: ""}, }, nil), "XML syntax error on line 1: element closed by ") }) t.Run("with_unordered_sqref", func(t *testing.T) { // Test deleting data validation when sqref has unordered ranges f := NewFile() ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).DataValidations = &xlsxDataValidations{ Count: 1, DataValidation: []*xlsxDataValidation{{ AllowBlank: true, ShowInputMessage: true, ShowErrorMessage: true, Type: "whole", Operator: "between", Sqref: "A5:A10 A15:A20 A3:A4", Formula1: &xlsxInnerXML{Content: "1"}, Formula2: &xlsxInnerXML{Content: "100"}, }}, } assert.NoError(t, f.DeleteDataValidation("Sheet1", "A7")) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dvs, 1) assert.Equal(t, "A3:A6 A8:A10 A15:A20", dvs[0].Sqref) }) t.Run("with_unordered_sqref_in_extLst", func(t *testing.T) { f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil, `A5:A10 A15:A20 A3:A4`, NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value, ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value)) f.checked = sync.Map{} assert.NoError(t, f.DeleteDataValidation("Sheet1", "A7")) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dvs, 1) assert.Equal(t, "A3:A6 A8:A10 A15:A20", dvs[0].Sqref) // Test deleting data validation with invalid sqref f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil, `A`, NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value, ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value)) f.checked = sync.Map{} assert.Equal(t, f.DeleteDataValidation("Sheet1", "A7"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) }) } ================================================ FILE: date.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "math" "time" ) const ( nanosInADay = float64((24 * time.Hour) / time.Nanosecond) dayNanoseconds = 24 * time.Hour maxDuration = 290 * 364 * dayNanoseconds roundEpsilon = 1e-9 ) var ( daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC) excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC) excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond) ) // timeToExcelTime provides a function to convert time to Excel time. func timeToExcelTime(t time.Time, date1904 bool) (float64, error) { date := excelMinTime1900 if date1904 { date = excel1904Epoc } if t.Before(date) { return 0, nil } tt, diff, result := t, t.Sub(date), 0.0 for diff >= maxDuration { result += float64(maxDuration / dayNanoseconds) tt = tt.Add(-maxDuration) diff = tt.Sub(date) } rem := diff % dayNanoseconds result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds) // Excel dates after 28th February 1900 are actually one day out. // Excel behaves as though the date 29th February 1900 existed, which it didn't. // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet // program that had the majority market share at the time; Lotus 1-2-3. // https://www.myonlinetraininghub.com/excel-date-and-time if !date1904 && t.After(excelBuggyPeriodStart) { result++ } return result, nil } // shiftJulianToNoon provides a function to process julian date to noon. func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) { switch { case -0.5 < julianFraction && julianFraction < 0.5: julianFraction += 0.5 case julianFraction >= 0.5: julianDays++ julianFraction -= 0.5 case julianFraction <= -0.5: julianDays-- julianFraction += 1.5 } return julianDays, julianFraction } // fractionOfADay provides a function to return the integer values for hour, // minutes, seconds and nanoseconds that comprised a given fraction of a day. // values would round to 1 us. func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) { const ( c1us = 1e3 c1s = 1e9 c1day = 24 * 60 * 60 * c1s ) frac := int64(c1day*fraction + c1us/2) nanoseconds = int((frac%c1s)/c1us) * c1us frac /= c1s seconds = int(frac % 60) frac /= 60 minutes = int(frac % 60) hours = int(frac / 60) return } // julianDateToGregorianTime provides a function to convert julian date to // gregorian time. func julianDateToGregorianTime(part1, part2 float64) time.Time { part1I, part1F := math.Modf(part1) part2I, part2F := math.Modf(part2) julianDays := part1I + part2I julianFraction := part1F + part2F julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction) day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays)) hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction) return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC) } // doTheFliegelAndVanFlandernAlgorithm; By this point generations of // programmers have repeated the algorithm sent to the editor of // "Communications of the ACM" in 1968 (published in CACM, volume 11, number // 10, October 1968, p.657). None of those programmers seems to have found it // necessary to explain the constants or variable names set out by Henry F. // Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that journal and // expand an explanation here - that day is not today. func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) { l := jd + 68569 n := (4 * l) / 146097 l = l - (146097*n+3)/4 i := (4000 * (l + 1)) / 1461001 l = l - (1461*i)/4 + 31 j := (80 * l) / 2447 d := l - (2447*j)/80 l = j / 11 m := j + 2 - (12 * l) y := 100*(n-49) + i + l return d, m, y } // timeFromExcelTime provides a function to convert an excelTime // representation (stored as a floating point number) to a time.Time. func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { var date time.Time wholeDaysPart := int(excelTime) // Excel uses Julian dates prior to March 1st 1900, and Gregorian // thereafter. if wholeDaysPart <= 61 { const OFFSET1900 = 15018.0 const OFFSET1904 = 16480.0 const MJD0 float64 = 2400000.5 var date time.Time if date1904 { date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904) } else { date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900) } return date } floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon if date1904 { date = excel1904Epoc } else { date = excel1900Epoc } durationPart := time.Duration(nanosInADay * floatPart) date = date.AddDate(0, 0, wholeDaysPart).Add(durationPart) if date.Nanosecond()/1e6 > 500 { return date.Round(time.Second) } return date.Truncate(time.Second) } // ExcelDateToTime converts a float-based Excel date representation to a time.Time. func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) { if excelDate < 0 { return time.Time{}, newInvalidExcelDateError(excelDate) } return timeFromExcelTime(excelDate, use1904Format), nil } // isLeapYear determine if leap year for a given year. func isLeapYear(y int) bool { if y == y/400*400 { return true } if y == y/100*100 { return false } return y == y/4*4 } // getDaysInMonth provides a function to get the days by a given year and // month number. func getDaysInMonth(y, m int) int { if m == 2 && isLeapYear(y) { return 29 } return daysInMonth[m-1] } // validateDate provides a function to validate if a valid date by a given // year, month, and day number. func validateDate(y, m, d int) bool { if m < 1 || m > 12 { return false } if d < 1 { return false } return d <= getDaysInMonth(y, m) } // formatYear converts the given year number into a 4-digit format. func formatYear(y int) int { if y < 1900 { if y < 30 { y += 2000 } else { y += 1900 } } return y } // getDurationNumFmt returns most simplify numbers format code for time // duration type cell value by given worksheet name, cell reference and number. func getDurationNumFmt(d time.Duration) int { if d >= time.Hour*24 { return 46 } // Whole minutes if d.Minutes() == float64(int(d.Minutes())) { return 20 } return 21 } // getTimeNumFmt returns most simplify numbers format code for time type cell // value by given worksheet name, cell reference and number. func getTimeNumFmt(t time.Time) int { nextMonth := t.AddDate(0, 1, 0) // Whole months if t.Day() == 1 && nextMonth.Day() == 1 { return 17 } // Whole days if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 { return 14 } return 22 } ================================================ FILE: date_test.go ================================================ package excelize import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" ) type dateTest struct { ExcelValue float64 GoValue time.Time } var trueExpectedDateList = []dateTest{ {0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)}, {25569.000000000000, time.Unix(0, 0).UTC()}, // Expected values extracted from real spreadsheet {1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)}, {1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)}, {1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)}, {1.0416666666666667, time.Date(1900, time.January, 1, 1, 0, 0, 0, time.UTC)}, {2.0000000000000000, time.Date(1900, time.January, 2, 0, 0, 0, 0, time.UTC)}, {43269.000000000000, time.Date(2018, time.June, 18, 0, 0, 0, 0, time.UTC)}, {43542.611111111109, time.Date(2019, time.March, 18, 14, 40, 0, 0, time.UTC)}, {401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)}, } var excelTimeInputList = []dateTest{ {0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)}, {60.0, time.Date(1900, 2, 28, 0, 0, 0, 0, time.UTC)}, {61.0, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC)}, {41275.0, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)}, {44450.3333333333, time.Date(2021, time.September, 11, 8, 0, 0, 0, time.UTC)}, {401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)}, } func TestTimeToExcelTime(t *testing.T) { for i, test := range trueExpectedDateList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { excelTime, err := timeToExcelTime(test.GoValue, false) assert.NoError(t, err) assert.Equalf(t, test.ExcelValue, excelTime, "Time: %s", test.GoValue.String()) }) } } func TestTimeToExcelTime_Timezone(t *testing.T) { location, err := time.LoadLocation("America/Los_Angeles") if !assert.NoError(t, err) { t.FailNow() } for i, test := range trueExpectedDateList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { _, err := timeToExcelTime(test.GoValue.In(location), false) assert.NoError(t, err) }) } } func TestTimeFromExcelTime(t *testing.T) { for i, test := range excelTimeInputList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { assert.Equal(t, test.GoValue, timeFromExcelTime(test.ExcelValue, false)) }) } for hour := 0; hour < 24; hour++ { for minVal := 0; minVal < 60; minVal++ { for sec := 0; sec < 60; sec++ { date := time.Date(2021, time.December, 30, hour, minVal, sec, 0, time.UTC) // Test use 1900 date system excel1900Time, err := timeToExcelTime(date, false) assert.NoError(t, err) date1900Out := timeFromExcelTime(excel1900Time, false) assert.EqualValues(t, hour, date1900Out.Hour()) assert.EqualValues(t, minVal, date1900Out.Minute()) assert.EqualValues(t, sec, date1900Out.Second()) // Test use 1904 date system excel1904Time, err := timeToExcelTime(date, true) assert.NoError(t, err) date1904Out := timeFromExcelTime(excel1904Time, true) assert.EqualValues(t, hour, date1904Out.Hour()) assert.EqualValues(t, minVal, date1904Out.Minute()) assert.EqualValues(t, sec, date1904Out.Second()) } } } } func TestTimeFromExcelTime_1904(t *testing.T) { julianDays, julianFraction := shiftJulianToNoon(1, -0.6) assert.Equal(t, julianDays, 0.0) assert.Equal(t, julianFraction, 0.9) julianDays, julianFraction = shiftJulianToNoon(1, 0.1) assert.Equal(t, julianDays, 1.0) assert.Equal(t, julianFraction, 0.6) assert.Equal(t, timeFromExcelTime(61, true), time.Date(1904, time.March, 2, 0, 0, 0, 0, time.UTC)) assert.Equal(t, timeFromExcelTime(62, true), time.Date(1904, time.March, 3, 0, 0, 0, 0, time.UTC)) } func TestExcelDateToTime(t *testing.T) { // Check normal case for i, test := range excelTimeInputList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { timeValue, err := ExcelDateToTime(test.ExcelValue, false) assert.Equal(t, test.GoValue, timeValue) assert.NoError(t, err) }) } // Check error case _, err := ExcelDateToTime(-1, false) assert.EqualError(t, err, newInvalidExcelDateError(-1).Error()) } ================================================ FILE: docProps.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" "reflect" "slices" "time" ) // SetAppProps provides a function to set document application properties. The // properties that can be set are: // // Property | Description // -------------------+-------------------------------------------------------------------------- // Application | The name of the application that created this document. // | // ScaleCrop | Indicates the display mode of the document thumbnail. Set this element // | to 'true' to enable scaling of the document thumbnail to the display. Set // | this element to 'false' to enable cropping of the document thumbnail to // | show only sections that will fit the display. // | // DocSecurity | Security level of a document as a numeric value. Document security is // | defined as: // | 1 - Document is password protected. // | 2 - Document is recommended to be opened as read-only. // | 3 - Document is enforced to be opened as read-only. // | 4 - Document is locked for annotation. // | // Company | The name of a company associated with the document. // | // LinksUpToDate | Indicates whether hyperlinks in a document are up-to-date. Set this // | element to 'true' to indicate that hyperlinks are updated. Set this // | element to 'false' to indicate that hyperlinks are outdated. // | // HyperlinksChanged | Specifies that one or more hyperlinks in this part were updated // | exclusively in this part by a producer. The next producer to open this // | document shall update the hyperlink relationships with the new // | hyperlinks specified in this part. // | // AppVersion | Specifies the version of the application which produced this document. // | The content of this element shall be of the form XX.YYYY where X and Y // | represent numerical values, or the document shall be considered // | non-conformant. // // For example: // // err := f.SetAppProps(&excelize.AppProperties{ // Application: "Microsoft Excel", // ScaleCrop: true, // DocSecurity: 3, // Company: "Company Name", // LinksUpToDate: true, // HyperlinksChanged: true, // AppVersion: "16.0000", // }) func (f *File) SetAppProps(appProperties *AppProperties) error { app := new(xlsxProperties) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { return err } setNoPtrFieldsVal([]string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"}, reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem()) app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value output, err := xml.Marshal(app) f.saveFileList(defaultXMLPathDocPropsApp, output) return err } // GetAppProps provides a function to get document application properties. func (f *File) GetAppProps() (ret *AppProperties, err error) { app := new(xlsxProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { return } ret, err = &AppProperties{ Application: app.Application, ScaleCrop: app.ScaleCrop, DocSecurity: app.DocSecurity, Company: app.Company, LinksUpToDate: app.LinksUpToDate, HyperlinksChanged: app.HyperlinksChanged, AppVersion: app.AppVersion, }, nil return } // SetDocProps provides a function to set document core properties. The // properties that can be set are: // // Property | Description // ----------------+----------------------------------------------------------- // Title | The name given to the resource. // | // Subject | The topic of the content of the resource. // | // Creator | An entity primarily responsible for making the content of // | the resource. // | // Keywords | A delimited set of keywords to support searching and // | indexing. This is typically a list of terms that are not // | available elsewhere in the properties. // | // Description | An explanation of the content of the resource. // | // LastModifiedBy | The user who performed the last modification. The // | identification is environment-specific. // | // Language | The language of the intellectual content of the resource. // | // Identifier | An unambiguous reference to the resource within a given // | context. // | // Revision | The topic of the content of the resource. // | // ContentStatus | The status of the content. For example: Values might // | include "Draft", "Reviewed" and "Final" // | // Category | A categorization of the content of this package. // | // Version | The version number. This value is set by the user or by // | the application. // | // Created | The created time of the content of the resource which // | represent in ISO 8601 UTC format, for example // | "2019-06-04T22:00:10Z". // | // Modified | The modified time of the content of the resource which // | represent in ISO 8601 UTC format, for example // | "2019-06-04T22:00:10Z". // | // // For example: // // err := f.SetDocProps(&excelize.DocProperties{ // Category: "category", // ContentStatus: "Draft", // Created: "2019-06-04T22:00:10Z", // Creator: "Go Excelize", // Description: "This file created by Go Excelize", // Identifier: "xlsx", // Keywords: "Spreadsheet", // LastModifiedBy: "Go Author", // Modified: "2019-06-04T22:00:10Z", // Revision: "0", // Subject: "Test Subject", // Title: "Test Title", // Language: "en-US", // Version: "1.0.0", // }) func (f *File) SetDocProps(docProperties *DocProperties) error { core := new(decodeCoreProperties) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { return err } newProps := &xlsxCoreProperties{ Dc: NameSpaceDublinCore, Dcterms: NameSpaceDublinCoreTerms, Dcmitype: NameSpaceDublinCoreMetadataInitiative, XSI: NameSpaceXMLSchemaInstance, Title: core.Title, Subject: core.Subject, Creator: core.Creator, Keywords: core.Keywords, Description: core.Description, LastModifiedBy: core.LastModifiedBy, Language: core.Language, Identifier: core.Identifier, Revision: core.Revision, ContentStatus: core.ContentStatus, Category: core.Category, Version: core.Version, } if core.Created != nil { newProps.Created = &xlsxDcTerms{Type: core.Created.Type, Text: core.Created.Text} } if core.Modified != nil { newProps.Modified = &xlsxDcTerms{Type: core.Modified.Type, Text: core.Modified.Text} } setNoPtrFieldsVal([]string{ "Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords", "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version", }, reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem()) if docProperties.Created != "" { newProps.Created = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Created} } if docProperties.Modified != "" { newProps.Modified = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Modified} } output, err := xml.Marshal(newProps) f.saveFileList(defaultXMLPathDocPropsCore, output) return err } // GetDocProps provides a function to get document core properties. func (f *File) GetDocProps() (ret *DocProperties, err error) { core := new(decodeCoreProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { return } ret, err = &DocProperties{ Category: core.Category, ContentStatus: core.ContentStatus, Creator: core.Creator, Description: core.Description, Identifier: core.Identifier, Keywords: core.Keywords, LastModifiedBy: core.LastModifiedBy, Revision: core.Revision, Subject: core.Subject, Title: core.Title, Language: core.Language, Version: core.Version, }, nil if core.Created != nil { ret.Created = core.Created.Text } if core.Modified != nil { ret.Modified = core.Modified.Text } return } // SetCustomProps provides a function to set custom file properties by given // property name and value. If the property name already exists, it will be // updated, otherwise a new property will be added. The value can be of type // int32, float64, bool, string, time.Time or nil. The property will be delete // if the value is nil. The function returns an error if the property value is // not of the correct type. func (f *File) SetCustomProps(prop CustomProperty) error { if prop.Name == "" { return ErrParameterInvalid } props := new(decodeCustomProperties) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))). Decode(props); err != nil && err != io.EOF { return err } customProps := xlsxCustomProperties{Vt: NameSpaceDocumentPropertiesVariantTypes.Value} idx, pID := -1, 1 for i := range props.Property { p := new(xlsxProperty) setPtrFields(reflect.ValueOf(&props.Property[i]).Elem(), reflect.ValueOf(p).Elem()) if pID < props.Property[i].PID { pID = props.Property[i].PID } if props.Property[i].Name == prop.Name { idx = i } customProps.Property = append(customProps.Property, *p) } if idx != -1 && prop.Value == nil { customProps.Property = slices.Delete(customProps.Property, idx, idx+1) } if prop.Value != nil { property := xlsxProperty{Name: prop.Name, FmtID: EXtURICustomPropertyFmtID} if err := property.setCustomProps(prop.Value); err != nil { return err } if idx != -1 { property.PID = customProps.Property[idx].PID customProps.Property[idx] = property } else { property.PID = pID + 1 customProps.Property = append(customProps.Property, property) } } _ = f.addRels(defaultXMLPathRels, SourceRelationshipCustomProperties, defaultXMLPathDocPropsCustom, "") if err := f.addContentTypePart(0, "customProperties"); err != nil { return err } output, err := xml.Marshal(customProps) f.saveFileList(defaultXMLPathDocPropsCustom, output) return err } // setCustomProps sets the custom property value based on its type. func (prop *xlsxProperty) setCustomProps(value interface{}) error { switch v := value.(type) { case int32: prop.I4 = &v case float64: prop.R8 = float64Ptr(v) case bool: prop.Bool = boolPtr(v) case string: prop.Lpwstr = stringPtr(value.(string)) case time.Time: prop.FileTime = stringPtr(value.(time.Time).Format(time.RFC3339)) default: return ErrParameterInvalid } return nil } // GetCustomProps provides a function to get custom file properties. func (f *File) GetCustomProps() ([]CustomProperty, error) { var customProps []CustomProperty props := new(decodeCustomProperties) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))). Decode(props); err != nil && err != io.EOF { return customProps, err } for _, p := range props.Property { prop := CustomProperty{Name: p.Name} var err error if prop.Value, err = p.getCustomProps(); err != nil { return customProps, err } customProps = append(customProps, prop) } return customProps, nil } // getCustomProps gets the custom property value based on its type. func (p *decodeProperty) getCustomProps() (interface{}, error) { s := reflect.ValueOf(p).Elem() for i := range s.NumField() { if 11 <= i && i <= 20 && !s.Field(i).IsNil() { return int32(s.Field(i).Elem().Int()), nil // Field vt:i1 to vt:uint } if 21 <= i && i <= 22 && !s.Field(i).IsNil() { return s.Field(i).Elem().Float(), nil // Field vt:r4 to vt:r8 } if p.Bool != nil { return *p.Bool, nil } if p.Lpwstr != nil { return *p.Lpwstr, nil } if p.FileTime != nil { return time.Parse(time.RFC3339, *p.FileTime) } } return nil, nil } ================================================ FILE: docProps_test.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "fmt" "path/filepath" "slices" "testing" "time" "github.com/stretchr/testify/assert" ) var MacintoshCyrillicCharset = []byte{0x8F, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x20, 0xEC, 0xE8, 0xF0} func TestSetAppProps(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.SetAppProps(&AppProperties{ Application: "Microsoft Excel", ScaleCrop: true, DocSecurity: 3, Company: "Company Name", LinksUpToDate: true, HyperlinksChanged: true, AppVersion: "16.0000", })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetAppProps.xlsx"))) f.Pkg.Store(defaultXMLPathDocPropsApp, nil) assert.NoError(t, f.SetAppProps(&AppProperties{})) assert.NoError(t, f.Close()) // Test unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) assert.EqualError(t, f.SetAppProps(&AppProperties{}), "XML syntax error on line 1: invalid UTF-8") } func TestGetAppProps(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } props, err := f.GetAppProps() assert.NoError(t, err) assert.Equal(t, props.Application, "Microsoft Macintosh Excel") f.Pkg.Store(defaultXMLPathDocPropsApp, nil) _, err = f.GetAppProps() assert.NoError(t, err) assert.NoError(t, f.Close()) // Test get application properties with unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) _, err = f.GetAppProps() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetDocProps(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.SetDocProps(&DocProperties{ Category: "category", ContentStatus: "Draft", Created: "2019-06-04T22:00:10Z", Creator: "Go Excelize", Description: "This file created by Go Excelize", Identifier: "xlsx", Keywords: "Spreadsheet", LastModifiedBy: "Go Author", Modified: "2019-06-04T22:00:10Z", Revision: "0", Subject: "Test Subject", Title: "Test Title", Language: "en-US", Version: "1.0.0", })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx"))) f.Pkg.Store(defaultXMLPathDocPropsCore, nil) assert.NoError(t, f.SetDocProps(&DocProperties{})) assert.NoError(t, f.Close()) // Test unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8") } func TestGetDocProps(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } props, err := f.GetDocProps() assert.NoError(t, err) assert.Equal(t, props.Creator, "Microsoft Office User") f.Pkg.Store(defaultXMLPathDocPropsCore, nil) _, err = f.GetDocProps() assert.NoError(t, err) assert.NoError(t, f.Close()) // Test get workbook properties with unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) _, err = f.GetDocProps() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCustomProps(t *testing.T) { f := NewFile() expected := []CustomProperty{ {Name: "Text Prop", Value: "text"}, {Name: "Boolean Prop 1", Value: true}, {Name: "Boolean Prop 2", Value: false}, {Name: "Number Prop 1", Value: -123.456}, {Name: "Number Prop 2", Value: int32(1)}, {Name: "Date Prop", Value: time.Date(2021, time.September, 11, 0, 0, 0, 0, time.UTC)}, } for _, prop := range expected { assert.NoError(t, f.SetCustomProps(prop)) } props, err := f.GetCustomProps() assert.NoError(t, err) assert.Equal(t, expected, props) // Test delete custom property assert.NoError(t, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 1", Value: nil})) props, err = f.GetCustomProps() assert.NoError(t, err) expected = slices.Delete(expected, 1, 2) assert.Equal(t, expected, props) // Test change custom property value data type assert.NoError(t, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 2", Value: "true"})) props, err = f.GetCustomProps() assert.NoError(t, err) assert.Equal(t, props[1].Value, "true") // Test set custom property with unsupported value data type assert.Equal(t, ErrParameterInvalid, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 2", Value: 1})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCustomProps.xlsx"))) assert.NoError(t, f.Close()) // Test set custom property without property name f = NewFile() assert.Equal(t, ErrParameterInvalid, f.SetCustomProps(CustomProperty{})) // Test set custom property with unsupported charset f.Pkg.Store(defaultXMLPathDocPropsCustom, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCustomProps(CustomProperty{Name: "Prop"}), "XML syntax error on line 1: invalid UTF-8") // Test get custom property with unsupported charset _, err = f.GetCustomProps() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test set custom property with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCustomProps(CustomProperty{Name: "Prop"}), "XML syntax error on line 1: invalid UTF-8") // Test get custom property with unsupported charset f.Pkg.Store(defaultXMLPathDocPropsCustom, []byte(fmt.Sprintf(`x`, NameSpaceDocumentPropertiesVariantTypes, EXtURICustomPropertyFmtID))) _, err = f.GetCustomProps() assert.EqualError(t, err, "parsing time \"x\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"x\" as \"2006\"") // Test get custom property with unsupported value data type f.Pkg.Store(defaultXMLPathDocPropsCustom, []byte(fmt.Sprintf(``, NameSpaceDocumentPropertiesVariantTypes, EXtURICustomPropertyFmtID))) props, err = f.GetCustomProps() assert.Equal(t, []CustomProperty{{Name: "Prop"}}, props) assert.NoError(t, err) assert.NoError(t, f.Close()) } ================================================ FILE: drawing.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" "reflect" "strconv" "strings" ) // prepareDrawing provides a function to prepare drawing ID and XML by given // drawingID, worksheet name and default drawingXML. func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) { sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" if ws.Drawing != nil { // The worksheet already has a picture or chart relationships, use the // relationships drawing ../drawings/drawing%d.xml or /xl/drawings/drawing%d.xml. sheetRelationshipsDrawingXML = strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "/xl/drawings/", "../drawings/") drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml")) drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl") } else { // Add first picture for given sheet. sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) } return drawingID, drawingXML } // prepareChartSheetDrawing provides a function to prepare drawing ID and XML // by given drawingID, worksheet name and default drawingXML. func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) { sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" // Only allow one chart in a chartsheet. sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/chartsheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetNameSpace(sheet, SourceRelationship) cs.Drawing = &xlsxDrawing{ RID: "rId" + strconv.Itoa(rID), } } // addChart provides a function to create chart as xl/charts/chart%d.xml by // given format sets. func (f *File) addChart(opts *Chart, comboCharts []*Chart) { count := f.countCharts() xlsxChartSpace := xlsxChartSpace{ XMLNSa: NameSpaceDrawingML.Value, Date1904: &attrValBool{Val: boolPtr(false)}, Lang: &attrValString{Val: stringPtr("en-US")}, RoundedCorners: &attrValBool{Val: boolPtr(false)}, Chart: cChart{ Title: f.drawPlotAreaTitles(opts.Title, ""), View3D: &cView3D{ RotX: &attrValInt{Val: intPtr(chartView3DRotX[opts.Type])}, RotY: &attrValInt{Val: intPtr(chartView3DRotY[opts.Type])}, Perspective: &attrValInt{Val: intPtr(chartView3DPerspective[opts.Type])}, RAngAx: &attrValInt{Val: intPtr(chartView3DRAngAx[opts.Type])}, }, Floor: &cThicknessSpPr{ Thickness: &attrValInt{Val: intPtr(0)}, }, SideWall: &cThicknessSpPr{ Thickness: &attrValInt{Val: intPtr(0)}, }, BackWall: &cThicknessSpPr{ Thickness: &attrValInt{Val: intPtr(0)}, }, PlotArea: &cPlotArea{}, PlotVisOnly: &attrValBool{Val: boolPtr(false)}, DispBlanksAs: &attrValString{Val: stringPtr(opts.ShowBlanksAs)}, ShowDLblsOverMax: &attrValBool{Val: boolPtr(false)}, }, SpPr: &cSpPr{ SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{Val: "bg1"}, }, Ln: f.drawChartLn(&opts.Border), }, PrintSettings: &cPrintSettings{ PageMargins: &cPageMargins{ B: 0.75, L: 0.7, R: 0.7, T: 0.7, Header: 0.3, Footer: 0.3, }, }, } xlsxChartSpace.SpPr = f.drawShapeFill(opts.Fill, xlsxChartSpace.SpPr) plotAreaFunc := map[ChartType]func(pa *cPlotArea, opts *Chart) *cPlotArea{ Area: f.drawBaseChart, AreaStacked: f.drawBaseChart, AreaPercentStacked: f.drawBaseChart, Area3D: f.drawBaseChart, Area3DStacked: f.drawBaseChart, Area3DPercentStacked: f.drawBaseChart, Bar: f.drawBaseChart, BarStacked: f.drawBaseChart, BarPercentStacked: f.drawBaseChart, Bar3DClustered: f.drawBaseChart, Bar3DStacked: f.drawBaseChart, Bar3DPercentStacked: f.drawBaseChart, Bar3DConeClustered: f.drawBaseChart, Bar3DConeStacked: f.drawBaseChart, Bar3DConePercentStacked: f.drawBaseChart, Bar3DPyramidClustered: f.drawBaseChart, Bar3DPyramidStacked: f.drawBaseChart, Bar3DPyramidPercentStacked: f.drawBaseChart, Bar3DCylinderClustered: f.drawBaseChart, Bar3DCylinderStacked: f.drawBaseChart, Bar3DCylinderPercentStacked: f.drawBaseChart, Col: f.drawBaseChart, ColStacked: f.drawBaseChart, ColPercentStacked: f.drawBaseChart, Col3D: f.drawBaseChart, Col3DClustered: f.drawBaseChart, Col3DStacked: f.drawBaseChart, Col3DPercentStacked: f.drawBaseChart, Col3DCone: f.drawBaseChart, Col3DConeClustered: f.drawBaseChart, Col3DConeStacked: f.drawBaseChart, Col3DConePercentStacked: f.drawBaseChart, Col3DPyramid: f.drawBaseChart, Col3DPyramidClustered: f.drawBaseChart, Col3DPyramidStacked: f.drawBaseChart, Col3DPyramidPercentStacked: f.drawBaseChart, Col3DCylinder: f.drawBaseChart, Col3DCylinderClustered: f.drawBaseChart, Col3DCylinderStacked: f.drawBaseChart, Col3DCylinderPercentStacked: f.drawBaseChart, Doughnut: f.drawDoughnutChart, Line: f.drawLineChart, Line3D: f.drawLine3DChart, Pie: f.drawPieChart, Pie3D: f.drawPie3DChart, PieOfPie: f.drawPieOfPieChart, BarOfPie: f.drawBarOfPieChart, Radar: f.drawRadarChart, Scatter: f.drawScatterChart, Surface3D: f.drawSurface3DChart, WireframeSurface3D: f.drawSurface3DChart, Contour: f.drawSurfaceChart, WireframeContour: f.drawSurfaceChart, Bubble: f.drawBubbleChart, Bubble3D: f.drawBubbleChart, StockHighLowClose: f.drawStockChart, StockOpenHighLowClose: f.drawStockChart, } xlsxChartSpace.Chart.drawChartLegend(opts) xlsxChartSpace.Chart.PlotArea.SpPr = f.drawShapeFill(opts.PlotArea.Fill, xlsxChartSpace.Chart.PlotArea.SpPr) xlsxChartSpace.Chart.PlotArea.DTable = f.drawPlotAreaDTable(opts) addChart := func(c, p *cPlotArea) { immutable, mutable := reflect.ValueOf(c).Elem(), reflect.ValueOf(p).Elem() for i := 0; i < mutable.NumField(); i++ { field := mutable.Field(i) if field.IsNil() { continue } fld := immutable.FieldByName(mutable.Type().Field(i).Name) if field.Kind() == reflect.Slice && i < 17 { // All []*cCharts type fields fld.Set(reflect.Append(fld, field.Index(0))) continue } fld.Set(field) } } addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](xlsxChartSpace.Chart.PlotArea, opts)) order := len(opts.Series) for idx := range comboCharts { comboCharts[idx].order = order addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](xlsxChartSpace.Chart.PlotArea, comboCharts[idx])) order += len(comboCharts[idx].Series) } // If the dateAx field exists, valAx field should be nil. if xlsxChartSpace.Chart.PlotArea != nil && xlsxChartSpace.Chart.PlotArea.DateAx != nil { xlsxChartSpace.Chart.PlotArea.CatAx = nil } chart, _ := xml.Marshal(xlsxChartSpace) media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml" f.saveFileList(media, chart) } // drawBaseChart provides a function to draw the c:plotArea element for bar, // and column series charts by given format sets. func (f *File) drawBaseChart(pa *cPlotArea, opts *Chart) *cPlotArea { c := []*cCharts{ { BarDir: &attrValString{ Val: stringPtr("col"), }, Grouping: &attrValString{ Val: stringPtr(plotAreaChartGrouping[opts.Type]), }, VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), Shape: f.drawChartShape(opts), DLbls: f.drawChartDLbls(opts), GapWidth: f.drawChartGapWidth(opts), AxID: f.genAxID(opts), Overlap: f.drawChartOverlap(opts), }, } var ok bool if *c[0].BarDir.Val, ok = plotAreaChartBarDir[opts.Type]; !ok { c[0].BarDir = nil if opts.XAxis.DropLines { c[0].DropLines = &cChartLines{} } } catAx := f.drawPlotAreaCatAx(pa, opts) valAx := f.drawPlotAreaValAx(pa, opts) charts := map[ChartType]*cPlotArea{ Area: { AreaChart: c, CatAx: catAx, ValAx: valAx, }, AreaStacked: { AreaChart: c, CatAx: catAx, ValAx: valAx, }, AreaPercentStacked: { AreaChart: c, CatAx: catAx, ValAx: valAx, }, Area3D: { Area3DChart: c, CatAx: catAx, ValAx: valAx, }, Area3DStacked: { Area3DChart: c, CatAx: catAx, ValAx: valAx, }, Area3DPercentStacked: { Area3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar: { BarChart: c, CatAx: catAx, ValAx: valAx, }, BarStacked: { BarChart: c, CatAx: catAx, ValAx: valAx, }, BarPercentStacked: { BarChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DConeClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DConeStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DConePercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DPyramidClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DPyramidStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DPyramidPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DCylinderClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DCylinderStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bar3DCylinderPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col: { BarChart: c, CatAx: catAx, ValAx: valAx, }, ColStacked: { BarChart: c, CatAx: catAx, ValAx: valAx, }, ColPercentStacked: { BarChart: c, CatAx: catAx, ValAx: valAx, }, Col3D: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DCone: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DConeClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DConeStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DConePercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DPyramid: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DPyramidClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DPyramidStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DPyramidPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DCylinder: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DCylinderClustered: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DCylinderStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Col3DCylinderPercentStacked: { Bar3DChart: c, CatAx: catAx, ValAx: valAx, }, Bubble: { BubbleChart: c, CatAx: catAx, ValAx: valAx, }, Bubble3D: { BubbleChart: c, CatAx: catAx, ValAx: valAx, }, } return charts[opts.Type] } // drawDoughnutChart provides a function to draw the c:plotArea element for // doughnut chart by given format sets. func (f *File) drawDoughnutChart(pa *cPlotArea, opts *Chart) *cPlotArea { holeSize := 75 if opts.HoleSize > 0 && opts.HoleSize <= 90 { holeSize = opts.HoleSize } return &cPlotArea{ DoughnutChart: []*cCharts{ { VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), HoleSize: &attrValInt{Val: intPtr(holeSize)}, }, }, } } // drawLineChart provides a function to draw the c:plotArea element for line // chart by given format sets. func (f *File) drawLineChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ LineChart: []*cCharts{ { Grouping: &attrValString{ Val: stringPtr(plotAreaChartGrouping[opts.Type]), }, VaryColors: &attrValBool{ Val: boolPtr(false), }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, CatAx: f.drawPlotAreaCatAx(pa, opts), ValAx: f.drawPlotAreaValAx(pa, opts), } if opts.XAxis.DropLines { plotArea.LineChart[0].DropLines = &cChartLines{} } if opts.XAxis.HighLowLines { plotArea.LineChart[0].HiLowLines = &cChartLines{} } return plotArea } // drawLine3DChart provides a function to draw the c:plotArea element for line // chart by given format sets. func (f *File) drawLine3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ Line3DChart: []*cCharts{ { Grouping: &attrValString{ Val: stringPtr(plotAreaChartGrouping[opts.Type]), }, VaryColors: &attrValBool{ Val: boolPtr(false), }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, CatAx: f.drawPlotAreaCatAx(pa, opts), ValAx: f.drawPlotAreaValAx(pa, opts), } if opts.XAxis.DropLines { plotArea.Line3DChart[0].DropLines = &cChartLines{} } return plotArea } // drawPieChart provides a function to draw the c:plotArea element for pie // chart by given format sets. func (f *File) drawPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ PieChart: []*cCharts{ { VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), }, }, } } // drawPie3DChart provides a function to draw the c:plotArea element for 3D // pie chart by given format sets. func (f *File) drawPie3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ Pie3DChart: []*cCharts{ { VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), }, }, } } // drawPieOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. func (f *File) drawPieOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { var splitPos *attrValInt if opts.PlotArea.SecondPlotValues > 0 { splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)} } return &cPlotArea{ OfPieChart: []*cCharts{ { OfPieType: &attrValString{ Val: stringPtr("pie"), }, VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), SplitPos: splitPos, SerLines: &attrValString{}, }, }, } } // drawBarOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. func (f *File) drawBarOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { var splitPos *attrValInt if opts.PlotArea.SecondPlotValues > 0 { splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)} } return &cPlotArea{ OfPieChart: []*cCharts{ { OfPieType: &attrValString{ Val: stringPtr("bar"), }, VaryColors: &attrValBool{ Val: opts.VaryColors, }, SplitPos: splitPos, Ser: f.drawChartSeries(opts), SerLines: &attrValString{}, }, }, } } // drawRadarChart provides a function to draw the c:plotArea element for radar // chart by given format sets. func (f *File) drawRadarChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ RadarChart: []*cCharts{ { RadarStyle: &attrValString{ Val: stringPtr("marker"), }, VaryColors: &attrValBool{ Val: boolPtr(false), }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, CatAx: f.drawPlotAreaCatAx(pa, opts), ValAx: f.drawPlotAreaValAx(pa, opts), } } // drawScatterChart provides a function to draw the c:plotArea element for // scatter chart by given format sets. func (f *File) drawScatterChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ ScatterChart: []*cCharts{ { ScatterStyle: &attrValString{ Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker }, VaryColors: &attrValBool{ Val: boolPtr(false), }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...), } } // drawSurface3DChart provides a function to draw the c:surface3DChart element by // given format sets. func (f *File) drawSurface3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ Surface3DChart: []*cCharts{ { Ser: f.drawChartSeries(opts), AxID: []*attrValInt{ {Val: intPtr(100000000)}, {Val: intPtr(100000001)}, {Val: intPtr(100000005)}, }, }, }, CatAx: f.drawPlotAreaCatAx(pa, opts), ValAx: f.drawPlotAreaValAx(pa, opts), SerAx: f.drawPlotAreaSerAx(opts), } if opts.Type == WireframeSurface3D { plotArea.Surface3DChart[0].Wireframe = &attrValBool{Val: boolPtr(true)} } return plotArea } // drawSurfaceChart provides a function to draw the c:surfaceChart element by // given format sets. func (f *File) drawSurfaceChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ SurfaceChart: []*cCharts{ { Ser: f.drawChartSeries(opts), AxID: []*attrValInt{ {Val: intPtr(100000000)}, {Val: intPtr(100000001)}, {Val: intPtr(100000005)}, }, }, }, CatAx: f.drawPlotAreaCatAx(pa, opts), ValAx: f.drawPlotAreaValAx(pa, opts), SerAx: f.drawPlotAreaSerAx(opts), } if opts.Type == WireframeContour { plotArea.SurfaceChart[0].Wireframe = &attrValBool{Val: boolPtr(true)} } return plotArea } // drawBubbleChart provides a function to draw the c:bubbleChart element by // given format sets. func (f *File) drawBubbleChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ BubbleChart: []*cCharts{ { VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...), } if opts.BubbleSize > 0 && opts.BubbleSize <= 300 { plotArea.BubbleChart[0].BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))} } return plotArea } // drawStockChart provides a function to draw the c:stockChart element by // given format sets. func (f *File) drawStockChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ StockChart: []*cCharts{ { VaryColors: &attrValBool{ Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, }, ValAx: f.drawPlotAreaValAx(pa, opts), DateAx: f.drawPlotAreaCatAx(pa, opts), } if opts.Type == StockHighLowClose { plotArea.StockChart[0].HiLowLines = &cChartLines{} } if opts.Type == StockOpenHighLowClose { plotArea.StockChart[0].HiLowLines = &cChartLines{} plotArea.StockChart[0].UpDownBars = &cUpDownBars{ GapWidth: &attrValString{Val: stringPtr("150")}, UpBars: &cChartLines{f.drawShapeFill(opts.PlotArea.UpBars.Fill, &cSpPr{Ln: f.drawChartLn(&opts.PlotArea.UpBars.Border)})}, DownBars: &cChartLines{f.drawShapeFill(opts.PlotArea.DownBars.Fill, &cSpPr{Ln: f.drawChartLn(&opts.PlotArea.UpBars.Border)})}, } } ser := *plotArea.StockChart[0].Ser ser[0].Val.NumRef.NumCache = &cNumCache{} return plotArea } // drawChartGapWidth provides a function to draw the c:gapWidth element by given // format sets. func (f *File) drawChartGapWidth(opts *Chart) *attrValInt { for _, t := range barColChartTypes { if t == opts.Type && opts.GapWidth != nil && *opts.GapWidth != 150 && *opts.GapWidth <= 500 { return &attrValInt{intPtr(int(*opts.GapWidth))} } } return nil } // drawChartOverlap provides a function to draw the c:overlap element by given // format sets. func (f *File) drawChartOverlap(opts *Chart) *attrValInt { var val *attrValInt if _, ok := plotAreaChartOverlap[opts.Type]; ok { val = &attrValInt{intPtr(100)} } if opts.Overlap != nil && -100 <= *opts.Overlap && *opts.Overlap <= 100 { val = &attrValInt{intPtr(*opts.Overlap)} } for _, t := range barColChartTypes { if t == opts.Type { return val } } return nil } // drawChartShape provides a function to draw the c:shape element by given // format sets. func (f *File) drawChartShape(opts *Chart) *attrValString { shapes := map[ChartType]string{ Bar3DConeClustered: "cone", Bar3DConeStacked: "cone", Bar3DConePercentStacked: "cone", Bar3DPyramidClustered: "pyramid", Bar3DPyramidStacked: "pyramid", Bar3DPyramidPercentStacked: "pyramid", Bar3DCylinderClustered: "cylinder", Bar3DCylinderStacked: "cylinder", Bar3DCylinderPercentStacked: "cylinder", Col3DCone: "cone", Col3DConeClustered: "cone", Col3DConeStacked: "cone", Col3DConePercentStacked: "cone", Col3DPyramid: "pyramid", Col3DPyramidClustered: "pyramid", Col3DPyramidStacked: "pyramid", Col3DPyramidPercentStacked: "pyramid", Col3DCylinder: "cylinder", Col3DCylinderClustered: "cylinder", Col3DCylinderStacked: "cylinder", Col3DCylinderPercentStacked: "cylinder", } if shape, ok := shapes[opts.Type]; ok { return &attrValString{Val: stringPtr(shape)} } return nil } // drawChartSeries provides a function to draw the c:ser element by given // format sets. func (f *File) drawChartSeries(opts *Chart) *[]cSer { var ser []cSer for k := range opts.Series { ser = append(ser, cSer{ IDx: &attrValInt{Val: intPtr(k + opts.order)}, Order: &attrValInt{Val: intPtr(k + opts.order)}, Tx: &cTx{ StrRef: &cStrRef{ F: opts.Series[k].Name, }, }, SpPr: f.drawChartSeriesSpPr(k, opts), Marker: f.drawChartSeriesMarker(k, opts), DPt: f.drawChartSeriesDPt(k, opts), DLbls: f.drawChartSeriesDLbls(k, opts), InvertIfNegative: &attrValBool{Val: boolPtr(false)}, Cat: f.drawChartSeriesCat(opts.Series[k], opts), Smooth: &attrValBool{Val: boolPtr(opts.Series[k].Line.Smooth)}, Val: f.drawChartSeriesVal(opts.Series[k], opts), XVal: f.drawChartSeriesXVal(opts.Series[k], opts), YVal: f.drawChartSeriesYVal(opts.Series[k], opts), BubbleSize: f.drawCharSeriesBubbleSize(opts.Series[k], opts), Bubble3D: f.drawCharSeriesBubble3D(opts), }) } return &ser } // drawShapeFill provides a function to draw the a:solidFill element by given // fill format sets. func (f *File) drawShapeFill(fill Fill, spPr *cSpPr) *cSpPr { if fill.Type == "pattern" && fill.Pattern == 1 { if spPr == nil { spPr = &cSpPr{} } if len(fill.Color) == 1 { spPr.SolidFill = &aSolidFill{SrgbClr: &aSrgbClr{Val: stringPtr(strings.TrimPrefix(fill.Color[0], "#"))}} if fill.Transparency > 0 { val := (100 - fill.Transparency) * 1000 spPr.SolidFill.SrgbClr.Alpha = &attrValInt{Val: &val} } return spPr } spPr.SolidFill = nil spPr.NoFill = stringPtr("") } return spPr } // drawChartSeriesSpPr provides a function to draw the c:spPr element by given // format sets. func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr { spPr := &cSpPr{SolidFill: &aSolidFill{SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa((opts.order+i)%6+1)}}} spPr = f.drawShapeFill(opts.Series[i].Fill, spPr) solid := &cSpPr{ Ln: &aLn{ W: f.ptToEMUs(opts.Series[i].Line.Width), Cap: "rnd", // rnd, sq, flat SolidFill: spPr.SolidFill, }, } if opts.Series[i].Line.Dash != ChartDashUnset { solid.Ln.PrstDash = &attrValString{Val: stringPtr(chartDashTypes[opts.Series[i].Line.Dash])} } noLn := &cSpPr{Ln: &aLn{NoFill: &attrValString{}}} if chartSeriesSpPr, ok := map[ChartType]map[ChartLineType]*cSpPr{ Line: {ChartLineUnset: solid, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: solid}, Scatter: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn}, StockHighLowClose: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn}, StockOpenHighLowClose: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn}, }[opts.Type]; ok { return chartSeriesSpPr[opts.Series[i].Line.Type] } if spPr.SolidFill.SrgbClr != nil { return spPr } return nil } // drawChartSeriesDPt provides a function to draw the c:dPt element by given // data index and format sets. func (f *File) drawChartSeriesDPt(i int, opts *Chart) []*cDPt { dpt := []*cDPt{{ IDx: &attrValInt{Val: intPtr(i)}, Bubble3D: &attrValBool{Val: boolPtr(false)}, SpPr: &cSpPr{ SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)}, }, Ln: &aLn{ W: 25400, Cap: "rnd", SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{Val: "lt1"}, }, }, Sp3D: &aSp3D{ ContourW: 25400, ContourClr: &aContourClr{ SchemeClr: &aSchemeClr{Val: "lt1"}, }, }, }, }} if len(opts.Series[i].DataPoint) > 0 { dpt = []*cDPt{} } for j := 0; j < len(opts.Series[i].DataPoint); j++ { spPr := &cSpPr{} dpt = append(dpt, &cDPt{ IDx: &attrValInt{Val: intPtr(opts.Series[i].DataPoint[j].Index)}, SpPr: f.drawShapeFill(opts.Series[i].DataPoint[j].Fill, spPr), }) } chartSeriesDPt := map[ChartType][]*cDPt{Doughnut: dpt, Pie: dpt, Pie3D: dpt} return chartSeriesDPt[opts.Type] } // drawChartSeriesCat provides a function to draw the c:cat element by given // chart series and format sets. func (f *File) drawChartSeriesCat(v ChartSeries, opts *Chart) *cCat { cat := &cCat{ StrRef: &cStrRef{ F: v.Categories, }, } chartSeriesCat := map[ChartType]*cCat{Scatter: nil, Bubble: nil, Bubble3D: nil} if _, ok := chartSeriesCat[opts.Type]; ok || v.Categories == "" { return nil } return cat } // drawChartSeriesVal provides a function to draw the c:val element by given // chart series and format sets. func (f *File) drawChartSeriesVal(v ChartSeries, opts *Chart) *cVal { val := &cVal{ NumRef: &cNumRef{ F: v.Values, NumCache: &cNumCache{}, }, } chartSeriesVal := map[ChartType]*cVal{Scatter: nil, Bubble: nil, Bubble3D: nil} if _, ok := chartSeriesVal[opts.Type]; ok { return nil } return val } // drawChartSeriesMarker provides a function to draw the c:marker element by // given data index and format sets. func (f *File) drawChartSeriesMarker(i int, opts *Chart) *cMarker { defaultSymbol := map[ChartType]*attrValString{ Scatter: {Val: stringPtr("circle")}, StockHighLowClose: {Val: stringPtr("dot")}, StockOpenHighLowClose: {Val: stringPtr("none")}, } marker := &cMarker{ Symbol: defaultSymbol[opts.Type], Size: &attrValInt{Val: intPtr(5)}, } if symbol := stringPtr(opts.Series[i].Marker.Symbol); *symbol != "" { marker.Symbol = &attrValString{Val: symbol} } if size := intPtr(opts.Series[i].Marker.Size); *size != 0 { marker.Size = &attrValInt{Val: size} } if i < 6 { marker.SpPr = &cSpPr{SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)}, }, Ln: &aLn{W: 9252}} } marker.SpPr = f.drawShapeFill(opts.Series[i].Marker.Fill, marker.SpPr) if marker.SpPr != nil && marker.SpPr.Ln != nil { marker.SpPr.Ln = f.drawChartLn(&opts.Series[i].Marker.Border) } chartSeriesMarker := map[ChartType]*cMarker{ Scatter: marker, Line: marker, StockHighLowClose: marker, StockOpenHighLowClose: marker, } return chartSeriesMarker[opts.Type] } // drawChartSeriesXVal provides a function to draw the c:xVal element by given // chart series and format sets. func (f *File) drawChartSeriesXVal(v ChartSeries, opts *Chart) *cCat { cat := &cCat{ StrRef: &cStrRef{ F: v.Categories, }, } chartSeriesXVal := map[ChartType]*cCat{Scatter: cat, Bubble: cat, Bubble3D: cat} return chartSeriesXVal[opts.Type] } // drawChartSeriesYVal provides a function to draw the c:yVal element by given // chart series and format sets. func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal { val := &cVal{ NumRef: &cNumRef{ F: v.Values, NumCache: &cNumCache{}, }, } chartSeriesYVal := map[ChartType]*cVal{Scatter: val, Bubble: val, Bubble3D: val} return chartSeriesYVal[opts.Type] } // drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize // element by given chart series and format sets. func (f *File) drawCharSeriesBubbleSize(v ChartSeries, opts *Chart) *cVal { if _, ok := map[ChartType]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok { return nil } fVal := v.Values if v.Sizes != "" { fVal = v.Sizes } return &cVal{ NumRef: &cNumRef{ F: fVal, NumCache: &cNumCache{}, }, } } // drawCharSeriesBubble3D provides a function to draw the c:bubble3D element // by given format sets. func (f *File) drawCharSeriesBubble3D(opts *Chart) *attrValBool { if _, ok := map[ChartType]bool{Bubble3D: true}[opts.Type]; !ok { return nil } return &attrValBool{Val: boolPtr(true)} } // drawChartNumFmt provides a function to draw the c:numFmt element by given // data labels format sets. func (f *File) drawChartNumFmt(labels ChartNumFmt) *cNumFmt { var numFmt *cNumFmt if labels.CustomNumFmt != "" || labels.SourceLinked { numFmt = &cNumFmt{ FormatCode: labels.CustomNumFmt, SourceLinked: labels.SourceLinked, } } return numFmt } // drawChartDLbls provides a function to draw the c:dLbls element by given // format sets. func (f *File) drawChartDLbls(opts *Chart) *cDLbls { return &cDLbls{ NumFmt: f.drawChartNumFmt(opts.PlotArea.NumFmt), ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)}, ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)}, ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)}, ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)}, ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)}, ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)}, ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)}, } } // inSupportedChartDataLabelsPositionType provides a method to check if an // element is present in an array, and return the index of its location, // otherwise return -1. func inSupportedChartDataLabelsPositionType(a []ChartDataLabelPositionType, x ChartDataLabelPositionType) int { for idx, n := range a { if x == n { return idx } } return -1 } // drawChartSeriesDLbls provides a function to draw the c:dLbls element by // given format sets. func (f *File) drawChartSeriesDLbls(i int, opts *Chart) *cDLbls { dLbls := f.drawChartDLbls(opts) chartSeriesDLbls := map[ChartType]*cDLbls{ Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, } if _, ok := chartSeriesDLbls[opts.Type]; ok { return nil } if types, ok := supportedChartDataLabelsPosition[opts.Type]; ok && opts.Series[i].DataLabelPosition != ChartDataLabelsPositionUnset { if inSupportedChartDataLabelsPositionType(types, opts.Series[i].DataLabelPosition) != -1 { dLbls.DLblPos = &attrValString{Val: stringPtr(chartDataLabelsPositionTypes[opts.Series[i].DataLabelPosition])} } } dLbl := opts.Series[i].DataLabel dLbls.SpPr = f.drawShapeFill(dLbl.Fill, dLbls.SpPr) dLbls.TxPr = &cTxPr{BodyPr: aBodyPr{}, P: aP{PPr: &aPPr{DefRPr: aRPr{}}}} drawChartFont(&dLbl.Font, &dLbls.TxPr.P.PPr.DefRPr) return dLbls } // drawPlotAreaCatAx provides a function to draw the c:catAx element. func (f *File) drawPlotAreaCatAx(pa *cPlotArea, opts *Chart) []*cAxs { maxVal := &attrValFloat{Val: opts.XAxis.Maximum} minVal := &attrValFloat{Val: opts.XAxis.Minimum} if opts.XAxis.Maximum == nil { maxVal = nil } if opts.XAxis.Minimum == nil { minVal = nil } ax := &cAxs{ AxID: &attrValInt{Val: intPtr(100000000)}, Scaling: &cScaling{ Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])}, Max: maxVal, Min: minVal, }, Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)}, AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])}, NumFmt: &cNumFmt{FormatCode: "General"}, MajorTickMark: &attrValString{Val: stringPtr("none")}, MinorTickMark: &attrValString{Val: stringPtr("none")}, Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""), TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.XAxis.TickLabelPosition])}, SpPr: f.drawPlotAreaSpPr(), TxPr: f.drawPlotAreaTxPr(&opts.XAxis), CrossAx: &attrValInt{Val: intPtr(100000001)}, Crosses: &attrValString{Val: stringPtr("autoZero")}, Auto: &attrValBool{Val: boolPtr(true)}, LblAlgn: &attrValString{Val: stringPtr("ctr")}, LblOffset: &attrValInt{Val: intPtr(100)}, NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, } if numFmt := f.drawChartNumFmt(opts.XAxis.NumFmt); numFmt != nil { ax.NumFmt = numFmt } if opts.XAxis.MajorGridLines { ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.XAxis.MinorGridLines { ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.XAxis.TickLabelSkip != 0 { ax.TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)} } if opts.order > 0 && opts.YAxis.Secondary && pa.CatAx != nil { ax.AxID = &attrValInt{Val: intPtr(opts.XAxis.axID)} ax.Delete = &attrValBool{Val: boolPtr(true)} ax.Crosses = nil ax.CrossAx = &attrValInt{Val: intPtr(opts.YAxis.axID)} return []*cAxs{pa.CatAx[0], ax} } return []*cAxs{ax} } // drawPlotAreaValAx provides a function to draw the c:valAx element. func (f *File) drawPlotAreaValAx(pa *cPlotArea, opts *Chart) []*cAxs { maxVal := &attrValFloat{Val: opts.YAxis.Maximum} minVal := &attrValFloat{Val: opts.YAxis.Minimum} if opts.YAxis.Maximum == nil { maxVal = nil } if opts.YAxis.Minimum == nil { minVal = nil } var logBase *attrValFloat if opts.YAxis.LogBase >= 2 && opts.YAxis.LogBase <= 1000 { logBase = &attrValFloat{Val: float64Ptr(opts.YAxis.LogBase)} } ax := &cAxs{ AxID: &attrValInt{Val: intPtr(100000001)}, Scaling: &cScaling{ LogBase: logBase, Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])}, Max: maxVal, Min: minVal, }, Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)}, AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])}, Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"), NumFmt: &cNumFmt{ FormatCode: chartValAxNumFmtFormatCode[opts.Type], }, MajorTickMark: &attrValString{Val: stringPtr("none")}, MinorTickMark: &attrValString{Val: stringPtr("none")}, TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])}, SpPr: f.drawPlotAreaSpPr(), TxPr: f.drawPlotAreaTxPr(&opts.YAxis), CrossAx: &attrValInt{Val: intPtr(100000000)}, Crosses: &attrValString{Val: stringPtr("autoZero")}, CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, } if numFmt := f.drawChartNumFmt(opts.YAxis.NumFmt); numFmt != nil { ax.NumFmt = numFmt } if opts.YAxis.MajorGridLines { ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.YAxis.MinorGridLines { ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if pos, ok := tickLblPosNone[opts.Type]; ok { ax.TickLblPos.Val = stringPtr(pos) } if opts.YAxis.MajorUnit != 0 { ax.MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)} } if opts.order > 0 && opts.YAxis.Secondary && pa.ValAx != nil { ax.AxID = &attrValInt{Val: intPtr(opts.YAxis.axID)} ax.AxPos = &attrValString{Val: stringPtr("r")} ax.Crosses = &attrValString{Val: stringPtr("max")} ax.CrossAx = &attrValInt{Val: intPtr(opts.XAxis.axID)} return []*cAxs{pa.ValAx[0], ax} } return []*cAxs{ax} } // drawPlotAreaSerAx provides a function to draw the c:serAx element. func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs { maxVal := &attrValFloat{Val: opts.YAxis.Maximum} minVal := &attrValFloat{Val: opts.YAxis.Minimum} if opts.YAxis.Maximum == nil { maxVal = nil } if opts.YAxis.Minimum == nil { minVal = nil } return []*cAxs{ { AxID: &attrValInt{Val: intPtr(100000005)}, Scaling: &cScaling{ Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])}, Max: maxVal, Min: minVal, }, Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)}, AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])}, TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])}, SpPr: f.drawPlotAreaSpPr(), TxPr: f.drawPlotAreaTxPr(nil), CrossAx: &attrValInt{Val: intPtr(100000001)}, }, } } // drawChartFont provides a function to draw the a:rPr element. func drawChartFont(fnt *Font, r *aRPr) { if fnt == nil { return } r.B = fnt.Bold r.I = fnt.Italic if idx := inStrSlice(supportedDrawingUnderlineTypes, fnt.Underline, true); idx != -1 { r.U = supportedDrawingUnderlineTypes[idx] } if fnt.Color != "" { if r.SolidFill == nil { r.SolidFill = &aSolidFill{} } r.SolidFill.SchemeClr = nil r.SolidFill.SrgbClr = &aSrgbClr{Val: stringPtr(strings.ReplaceAll(strings.ToUpper(fnt.Color), "#", ""))} } if fnt.Family != "" { if r.Latin == nil { r.Latin = &xlsxCTTextFont{} } if r.Ea == nil { r.Ea = &xlsxCTTextFont{} } if r.Cs == nil { r.Cs = &xlsxCTTextFont{} } r.Latin.Typeface = fnt.Family r.Ea.Typeface = fnt.Family r.Cs.Typeface = fnt.Family } if fnt.Size > 0 { r.Sz = fnt.Size * 100 } if fnt.Strike { r.Strike = "sngStrike" } } // drawPlotAreaTitles provides a function to draw the c:title element. func (f *File) drawPlotAreaTitles(runs []RichTextRun, vert string) *cTitle { if len(runs) == 0 { return nil } title := &cTitle{Tx: cTx{Rich: &cRich{}}, Overlay: &attrValBool{Val: boolPtr(false)}} for _, run := range runs { r := &aR{T: run.Text} drawChartFont(run.Font, &r.RPr) title.Tx.Rich.P = append(title.Tx.Rich.P, aP{ PPr: &aPPr{DefRPr: aRPr{}}, R: r, EndParaRPr: &aEndParaRPr{Lang: "en-US", AltLang: "en-US"}, }) } if vert == "horz" { title.Tx.Rich.BodyPr = aBodyPr{Rot: -5400000, Vert: vert} } return title } // drawPlotAreaDTable provides a function to draw the c:dTable element. func (f *File) drawPlotAreaDTable(opts *Chart) *cDTable { if _, ok := plotAreaChartGrouping[opts.Type]; ok && opts.PlotArea.ShowDataTable { return &cDTable{ ShowHorzBorder: &attrValBool{Val: boolPtr(true)}, ShowVertBorder: &attrValBool{Val: boolPtr(true)}, ShowOutline: &attrValBool{Val: boolPtr(true)}, ShowKeys: &attrValBool{Val: boolPtr(opts.PlotArea.ShowDataTableKeys)}, } } return nil } // drawPlotAreaSpPr provides a function to draw the c:spPr element. func (f *File) drawPlotAreaSpPr() *cSpPr { return &cSpPr{ Ln: &aLn{ W: 9525, Cap: "flat", Cmpd: "sng", Algn: "ctr", SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{ Val: "tx1", LumMod: &attrValInt{Val: intPtr(15000)}, LumOff: &attrValInt{Val: intPtr(85000)}, }, }, }, } } // drawPlotAreaTxPr provides a function to draw the c:txPr element. func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr { cTxPr := &cTxPr{ BodyPr: aBodyPr{ Rot: -60000000, SpcFirstLastPara: true, VertOverflow: "ellipsis", Vert: "horz", Wrap: "square", Anchor: "ctr", AnchorCtr: true, }, P: aP{ PPr: &aPPr{ DefRPr: aRPr{ Sz: 900, B: false, I: false, U: "none", Strike: "noStrike", Kern: 1200, Baseline: 0, SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{ Val: "tx1", LumMod: &attrValInt{Val: intPtr(15000)}, LumOff: &attrValInt{Val: intPtr(85000)}, }, }, Latin: &xlsxCTTextFont{Typeface: "+mn-lt"}, Ea: &xlsxCTTextFont{Typeface: "+mn-ea"}, Cs: &xlsxCTTextFont{Typeface: "+mn-cs"}, }, }, EndParaRPr: &aEndParaRPr{Lang: "en-US"}, }, } if opts != nil { drawChartFont(&opts.Font, &cTxPr.P.PPr.DefRPr) if -90 <= opts.Alignment.TextRotation && opts.Alignment.TextRotation <= 90 { cTxPr.BodyPr.Rot = opts.Alignment.TextRotation * 60000 } if idx := inStrSlice(supportedDrawingTextVerticalType, opts.Alignment.Vertical, true); idx != -1 { cTxPr.BodyPr.Vert = supportedDrawingTextVerticalType[idx] } } return cTxPr } // drawChartLn provides a function to draw the a:ln element. func (f *File) drawChartLn(opts *ChartLine) *aLn { ln := &aLn{ W: f.ptToEMUs(opts.Width), Cap: "flat", Cmpd: "sng", Algn: "ctr", } if opts.Dash != ChartDashUnset { ln.PrstDash = &attrValString{Val: stringPtr(chartDashTypes[opts.Dash])} } switch opts.Type { case ChartLineSolid: ln.SolidFill = f.drawShapeFill(opts.Fill, &cSpPr{ SolidFill: &aSolidFill{ SchemeClr: &aSchemeClr{ Val: "tx1", LumMod: &attrValInt{ Val: intPtr(15000), }, LumOff: &attrValInt{ Val: intPtr(85000), }, }, }, }).SolidFill return ln case ChartLineNone: ln.NoFill = &attrValString{} return ln default: return nil } } // drawChartLegend provides a function to draw the c:legend element. func (c *cChart) drawChartLegend(opts *Chart) { if opts.Legend.Position == "none" { c.Legend = nil return } if c.Legend == nil { c.Legend = &cLegend{ LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])}, Overlay: &attrValBool{Val: boolPtr(false)}, } } if opts.Legend.Font != nil { c.Legend.TxPr = &cTxPr{P: aP{PPr: &aPPr{}}} drawChartFont(opts.Legend.Font, &c.Legend.TxPr.P.PPr.DefRPr) } for k := range opts.Series { font := opts.Series[k].Legend.Font if font == nil { continue } legendEntry := cLegendEntry{ IDx: &attrValInt{Val: intPtr(k + opts.order)}, TxPr: &cTxPr{P: aP{PPr: &aPPr{}}}, } drawChartFont(font, &legendEntry.TxPr.P.PPr.DefRPr) c.Legend.LegendEntry = append(c.Legend.LegendEntry, legendEntry) } } // drawingParser provides a function to parse drawingXML. In order to solve // the problem that the label structure is changed after serialization and // deserialization, two different structures: decodeWsDr and encodeWsDr are // defined. func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) { var ( err error ok bool ) _, ok = f.Drawings.Load(path) if !ok { content := xlsxWsDr{ NS: NameSpaceDrawingMLSpreadSheet.Value, Xdr: NameSpaceDrawingMLSpreadSheet.Value, A: NameSpaceDrawingML.Value, } if _, ok = f.Pkg.Load(path); ok { // Append Model decodeWsDr := decodeWsDr{} if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). Decode(&decodeWsDr); err != nil && err != io.EOF { return nil, 0, err } content.R = decodeWsDr.R for _, v := range decodeWsDr.AlternateContent { content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{ Content: v.Content, XMLNSMC: SourceRelationshipCompatibility.Value, }) } for _, v := range decodeWsDr.OneCellAnchor { content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } for _, v := range decodeWsDr.TwoCellAnchor { content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } } f.Drawings.Store(path, &content) } var wsDr *xlsxWsDr if drawing, ok := f.Drawings.Load(path); ok && drawing != nil { wsDr = drawing.(*xlsxWsDr) } wsDr.mu.Lock() defer wsDr.mu.Unlock() return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2, nil } // addDrawingChart provides a function to add chart graphic frame by given // sheet, drawingXML, cell, width, height, relationship index and format sets. func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *GraphicOptions) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } width = int(float64(width) * opts.ScaleX) height = int(float64(height) * opts.ScaleY) colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = opts.Positioning from := xlsxFrom{} from.Col = colStart from.ColOff = x1 * EMU from.Row = rowStart from.RowOff = y1 * EMU to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU to.Row = rowEnd to.RowOff = y2 * EMU twoCellAnchor.From = &from twoCellAnchor.To = &to graphicFrame := xlsxGraphicFrame{ NvGraphicFramePr: xlsxNvGraphicFramePr{ CNvPr: &xlsxCNvPr{ ID: cNvPrID, Name: "Chart " + strconv.Itoa(cNvPrID), Descr: opts.AltText, }, }, Graphic: &xlsxGraphic{ GraphicData: &xlsxGraphicData{ URI: NameSpaceDrawingMLChart.Value, Chart: &xlsxChart{ C: NameSpaceDrawingMLChart.Value, R: SourceRelationship.Value, RID: "rId" + strconv.Itoa(rID), }, }, }, } if len(opts.Name) > 0 { graphicFrame.NvGraphicFramePr.CNvPr.Name = opts.Name } graphic, _ := xml.Marshal(graphicFrame) twoCellAnchor.GraphicFrame = string(graphic) twoCellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Locked, FPrintsWithSheet: *opts.PrintObject, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings.Store(drawingXML, content) return err } // addSheetDrawingChart provides a function to add chart graphic frame for // chartsheet by given sheet, drawingXML, width, height, relationship index // and format sets. func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *GraphicOptions) error { content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err } absoluteAnchor := xdrCellAnchor{ EditAs: opts.Positioning, Pos: &xlsxPoint2D{}, Ext: &xlsxPositiveSize2D{Cx: 9280533, Cy: 6051719}, } graphicFrame := xlsxGraphicFrame{ NvGraphicFramePr: xlsxNvGraphicFramePr{ CNvPr: &xlsxCNvPr{ ID: cNvPrID, Name: "Chart " + strconv.Itoa(cNvPrID), }, }, Graphic: &xlsxGraphic{ GraphicData: &xlsxGraphicData{ URI: NameSpaceDrawingMLChart.Value, Chart: &xlsxChart{ C: NameSpaceDrawingMLChart.Value, R: SourceRelationship.Value, RID: "rId" + strconv.Itoa(rID), }, }, }, } graphic, _ := xml.Marshal(graphicFrame) absoluteAnchor.GraphicFrame = string(graphic) absoluteAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Locked, FPrintsWithSheet: *opts.PrintObject, } content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor) f.Drawings.Store(drawingXML, content) return err } // deleteDrawing provides a function to delete the chart graphic frame and // returns deleted embed relationships ID (for unique picture cell anchor) by // given coordinates and graphic type. func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) ([]string, error) { var ( err error rID string delRID, refRID []string rIDMaps = map[string]int{} wsDr *xlsxWsDr deCellAnchor *decodeCellAnchor ) xdrCellAnchorFuncs := map[string]func(anchor *xdrCellAnchor) bool{ "Chart": func(anchor *xdrCellAnchor) bool { return anchor.Pic == nil }, "Pic": func(anchor *xdrCellAnchor) bool { return anchor.Pic != nil }, } decodeCellAnchorFuncs := map[string]func(anchor *decodeCellAnchor) bool{ "Chart": func(anchor *decodeCellAnchor) bool { return anchor.Pic == nil }, "Pic": func(anchor *decodeCellAnchor) bool { return anchor.Pic != nil }, } onAnchorCell := func(c, r int) bool { return c == col && r == row } if wsDr, _, err = f.drawingParser(drawingXML); err != nil { return delRID, err } deleteCellAnchor := func(ca []*xdrCellAnchor) ([]*xdrCellAnchor, error) { for idx := 0; idx < len(ca); idx++ { if err = nil; ca[idx].From != nil && xdrCellAnchorFuncs[drawingType](ca[idx]) { rID = extractEmbedRID(ca[idx].Pic, nil) rIDMaps[rID]++ if onAnchorCell(ca[idx].From.Col, ca[idx].From.Row) { refRID = append(refRID, rID) ca = append(ca[:idx], ca[idx+1:]...) idx-- rIDMaps[rID]-- } continue } deCellAnchor = new(decodeCellAnchor) if err = f.xmlNewDecoder(strings.NewReader("" + ca[idx].GraphicFrame + "")). Decode(deCellAnchor); err != nil && err != io.EOF { return ca, err } if err = nil; deCellAnchor.From != nil && decodeCellAnchorFuncs[drawingType](deCellAnchor) { rID = extractEmbedRID(nil, deCellAnchor.Pic) rIDMaps[rID]++ if onAnchorCell(deCellAnchor.From.Col, deCellAnchor.From.Row) { refRID = append(refRID, rID) ca = append(ca[:idx], ca[idx+1:]...) idx-- rIDMaps[rID]-- } } } return ca, err } if wsDr.OneCellAnchor, err = deleteCellAnchor(wsDr.OneCellAnchor); err != nil { return delRID, err } if wsDr.TwoCellAnchor, err = deleteCellAnchor(wsDr.TwoCellAnchor); err != nil { return delRID, err } f.Drawings.Store(drawingXML, wsDr) return getUnusedCellAnchorRID(delRID, refRID, rIDMaps), err } // extractEmbedRID returns embed relationship ID by giving cell anchor. func extractEmbedRID(pic *xlsxPic, decodePic *decodePic) string { var rID string if pic != nil { rID = pic.BlipFill.Blip.Embed } if decodePic != nil { rID = decodePic.BlipFill.Blip.Embed } return rID } // getUnusedCellAnchorRID returns relationship ID lists in the cell anchor which // for remove. func getUnusedCellAnchorRID(delRID, refRID []string, rIDMaps map[string]int) []string { for _, rID := range refRID { if rIDMaps[rID] == 0 && inStrSlice(delRID, rID, false) == -1 { delRID = append(delRID, rID) } } return delRID } // deleteDrawingRels provides a function to delete relationships in // xl/drawings/_rels/drawings%d.xml.rels by giving drawings relationships path // and relationship ID. func (f *File) deleteDrawingRels(rels, rID string) { drawingRels, _ := f.relsReader(rels) if drawingRels == nil { drawingRels = &xlsxRelationships{} } drawingRels.mu.Lock() defer drawingRels.mu.Unlock() for k, v := range drawingRels.Relationships { if v.ID == rID { drawingRels.Relationships = append(drawingRels.Relationships[:k], drawingRels.Relationships[k+1:]...) } } f.Relationships.Store(rels, drawingRels) } // genAxID provides a function to generate ID for primary and secondary // horizontal or vertical axis. func (f *File) genAxID(opts *Chart) []*attrValInt { opts.XAxis.axID, opts.YAxis.axID = 100000000, 100000001 if opts.order > 0 && opts.YAxis.Secondary { opts.XAxis.axID, opts.YAxis.axID = 100000003, 100000004 } return []*attrValInt{{Val: intPtr(opts.XAxis.axID)}, {Val: intPtr(opts.YAxis.axID)}} } ================================================ FILE: drawing_test.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestDrawingParser(t *testing.T) { f := File{ Drawings: sync.Map{}, Pkg: sync.Map{}, } f.Pkg.Store("charset", MacintoshCyrillicCharset) f.Pkg.Store("wsDr", []byte(xml.Header+``)) // Test with one cell anchor _, _, err := f.drawingParser("wsDr") assert.NoError(t, err) // Test with unsupported charset _, _, err = f.drawingParser("charset") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test with alternate content f.Drawings = sync.Map{} f.Pkg.Store("wsDr", []byte(xml.Header+``)) _, _, err = f.drawingParser("wsDr") assert.NoError(t, err) } func TestDeleteDrawingRels(t *testing.T) { f := NewFile() // Test delete drawing relationships with unsupported charset rels := "xl/drawings/_rels/drawing1.xml.rels" f.Relationships.Delete(rels) f.Pkg.Store(rels, MacintoshCyrillicCharset) f.deleteDrawingRels(rels, "") } ================================================ FILE: errors.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "errors" "fmt" "strings" ) var ( // ErrAddVBAProject defined the error message on add the VBA project in // the workbook. ErrAddVBAProject = errors.New("unsupported VBA project") // ErrAttrValBool defined the error message on marshal and unmarshal // boolean type XML attribute. ErrAttrValBool = errors.New("unexpected child of attrValBool") // ErrCellCharsLength defined the error message for receiving a cell // characters length that exceeds the limit. ErrCellCharsLength = fmt.Errorf("cell value must be 0-%d characters", TotalCellChars) // ErrCellStyles defined the error message on cell styles exceeds the limit. ErrCellStyles = fmt.Errorf("the cell styles exceeds the %d limit", MaxCellStyles) // ErrColumnNumber defined the error message on receive an invalid column // number. ErrColumnNumber = fmt.Errorf("the column number must be greater than or equal to %d and less than or equal to %d", MinColumns, MaxColumns) // ErrColumnWidth defined the error message on receive an invalid column // width. ErrColumnWidth = fmt.Errorf("the width of the column must be less than or equal to %d characters", MaxColumnWidth) // ErrCoordinates defined the error message on invalid coordinates tuples // length. ErrCoordinates = errors.New("coordinates length must be 4") // ErrCustomNumFmt defined the error message on receive the empty custom // number format. ErrCustomNumFmt = errors.New("custom number format can not be empty") // ErrDataValidationFormulaLength defined the error message for receiving a // data validation formula length that exceeds the limit. ErrDataValidationFormulaLength = fmt.Errorf("data validation must be 0-%d characters", MaxFieldLength) // ErrDataValidationRange defined the error message on set decimal range // exceeds limit. ErrDataValidationRange = errors.New("data validation range exceeds limit") // ErrDefinedNameDuplicate defined the error message on the same name // already exists on the scope. ErrDefinedNameDuplicate = errors.New("the same name already exists on the scope") // ErrDefinedNameScope defined the error message on not found defined name // in the given scope. ErrDefinedNameScope = errors.New("no defined name on the scope") // ErrExistsSheet defined the error message on given sheet already exists. ErrExistsSheet = errors.New("the same name sheet already exists") // ErrExistsTableName defined the error message on given table already // exists. ErrExistsTableName = errors.New("the same name table already exists") // ErrFillType defined the error message on receive an invalid fill type. ErrFillType = errors.New("fill type value must be one of 'gradient' or 'pattern'") // ErrFillGradientColor defined the error message on receive an invalid fill // color for 'gradient' type. ErrFillGradientColor = errors.New("fill color value must be an array of two colors for 'gradient' type") // ErrFillGradientShading defined the error message on receive an invalid // fill shading for 'gradient' type. ErrFillGradientShading = errors.New("fill shading value must be between 0 and 16 for 'gradient' type") // ErrFillPatternColor defined the error message on receive an invalid fill // color for 'pattern' type. ErrFillPatternColor = errors.New("fill color value must be empty or an array of one color for 'pattern' type") // ErrFillPattern defined the error message on receive an invalid fill // pattern. ErrFillPattern = errors.New("fill pattern value must be between 0 and 18") // ErrFontLength defined the error message on the length of the font // family name overflow. ErrFontLength = fmt.Errorf("the length of the font family name must be less than or equal to %d", MaxFontFamilyLength) // ErrFontSize defined the error message on the size of the font is invalid. ErrFontSize = fmt.Errorf("font size must be an integer from %d to %d points", MinFontSize, MaxFontSize) // ErrFormControlValue defined the error message for receiving a scroll // value exceeds limit. ErrFormControlValue = fmt.Errorf("scroll value must be an integer from 0 to %d", MaxFormControlValue) // ErrGroupSheets defined the error message on group sheets. ErrGroupSheets = errors.New("group worksheet must contain an active worksheet") // ErrImgExt defined the error message on receive an unsupported image // extension. ErrImgExt = errors.New("unsupported image extension") // ErrInvalidFormula defined the error message on receive an invalid // formula. ErrInvalidFormula = errors.New("formula not valid") // ErrMaxFilePathLength defined the error message on receive the file path // length overflow. ErrMaxFilePathLength = fmt.Errorf("file path length exceeds maximum limit %d characters", MaxFilePathLength) // ErrMaxRowHeight defined the error message on receive an invalid row // height. ErrMaxRowHeight = fmt.Errorf("the height of the row must be less than or equal to %d points", MaxRowHeight) // ErrMaxRows defined the error message on receive a row number exceeds // maximum limit. ErrMaxRows = errors.New("row number exceeds maximum limit") // ErrNameLength defined the error message on receiving the defined name or // table name length exceeds the limit. ErrNameLength = fmt.Errorf("the name length exceeds the %d characters limit", MaxFieldLength) // ErrMaxGraphicAltTextLength defined the error message on receiving the // graphic alt text length exceeds the limit. ErrMaxGraphicAltTextLength = fmt.Errorf("the alt text length exceeds the %d characters limit", MaxGraphicAltTextLength) // ErrMaxGraphicNameLength defined the error message on receiving the // graphic name length exceeds the limit. ErrMaxGraphicNameLength = fmt.Errorf("the name length exceeds the %d characters limit", MaxGraphicNameLength) // ErrOptionsUnzipSizeLimit defined the error message for receiving // invalid UnzipSizeLimit and UnzipXMLSizeLimit. ErrOptionsUnzipSizeLimit = errors.New("the value of UnzipSizeLimit should be greater than or equal to UnzipXMLSizeLimit") // ErrOutlineLevel defined the error message on receive an invalid outline // level number. ErrOutlineLevel = errors.New("invalid outline level") // ErrPageSetupAdjustTo defined the error message for receiving a page setup // adjust to value exceeds limit. ErrPageSetupAdjustTo = errors.New("adjust to value must be an integer from 0 to 400") // ErrParameterInvalid defined the error message on receive the invalid // parameter. ErrParameterInvalid = errors.New("parameter is invalid") // ErrParameterRequired defined the error message on receive the empty // parameter. ErrParameterRequired = errors.New("parameter is required") // ErrPasswordLengthInvalid defined the error message on invalid password // length. ErrPasswordLengthInvalid = errors.New("password length invalid") // ErrPivotTableClassicLayout defined the error message on enable // ClassicLayout and CompactData in the same time. ErrPivotTableClassicLayout = errors.New("cannot enable ClassicLayout and CompactData in the same time") // ErrSave defined the error message for saving file. ErrSave = errors.New("no path defined for file, consider File.WriteTo or File.Write") // ErrSheetIdx defined the error message on receive the invalid worksheet // index. ErrSheetIdx = errors.New("invalid worksheet index") // ErrSheetNameBlank defined the error message on receive the blank sheet // name. ErrSheetNameBlank = errors.New("the sheet name can not be blank") // ErrSheetNameInvalid defined the error message on receive the sheet name // contains invalid characters. ErrSheetNameInvalid = errors.New("the sheet can not contain any of the characters :\\/?*[or]") // ErrSheetNameLength defined the error message on receiving the sheet // name length exceeds the limit. ErrSheetNameLength = fmt.Errorf("the sheet name length exceeds the %d characters limit", MaxSheetNameLength) // ErrSheetNameSingleQuote defined the error message on the first or last // character of the sheet name was a single quote. ErrSheetNameSingleQuote = errors.New("the first or last character of the sheet name can not be a single quote") // ErrSparkline defined the error message on receive the invalid sparkline // parameters. ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters") // ErrSparklineLocation defined the error message on missing Location // parameters ErrSparklineLocation = errors.New("parameter 'Location' is required") // ErrSparklineRange defined the error message on missing sparkline Range // parameters ErrSparklineRange = errors.New("parameter 'Range' is required") // ErrSparklineStyle defined the error message on receive the invalid // sparkline Style parameters. ErrSparklineStyle = errors.New("parameter 'Style' value must be an integer from 0 to 35") // ErrSparklineType defined the error message on receive the invalid // sparkline Type parameters. ErrSparklineType = errors.New("parameter 'Type' value must be one of 'line', 'column' or 'win_loss'") // ErrTotalSheetHyperlinks defined the error message on hyperlinks count // overflow. ErrTotalSheetHyperlinks = errors.New("over maximum limit hyperlinks in a worksheet") // ErrTransparency defined the error message for receiving a transparency // value exceeds limit. ErrTransparency = errors.New("transparency value must be an integer from 0 to 100") // ErrUnknownEncryptMechanism defined the error message on unsupported // encryption mechanism. ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism") // ErrUnprotectSheet defined the error message on worksheet has set no // protection. ErrUnprotectSheet = errors.New("worksheet has set no protect") // ErrUnprotectSheetPassword defined the error message on remove sheet // protection with password verification failed. ErrUnprotectSheetPassword = errors.New("worksheet protect password not match") // ErrUnprotectWorkbook defined the error message on workbook has set no // protection. ErrUnprotectWorkbook = errors.New("workbook has set no protect") // ErrUnprotectWorkbookPassword defined the error message on remove workbook // protection with password verification failed. ErrUnprotectWorkbookPassword = errors.New("workbook protect password not match") // ErrUnsupportedEncryptMechanism defined the error message on unsupported // encryption mechanism. ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism") // ErrUnsupportedHashAlgorithm defined the error message on unsupported // hash algorithm. ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm") // ErrUnsupportedNumberFormat defined the error message on unsupported // number format expression. ErrUnsupportedNumberFormat = errors.New("unsupported number format token") // ErrWorkbookFileFormat defined the error message on receive an // unsupported workbook file format. ErrWorkbookFileFormat = errors.New("unsupported workbook file format") // ErrWorkbookPassword defined the error message on receiving the incorrect // workbook password. ErrWorkbookPassword = errors.New("the supplied open workbook password is not correct") ) // ErrSheetNotExist defined an error of sheet that does not exist. type ErrSheetNotExist struct { SheetName string } // Error returns the error message on receiving the non existing sheet name. func (err ErrSheetNotExist) Error() string { return fmt.Sprintf("sheet %s does not exist", err.SheetName) } // newAddCommentError defined the error message on the comment already exist in // the cell. func newAddCommentError(cell string) error { return fmt.Errorf("comment already exist on cell %s", cell) } // newCellNameToCoordinatesError defined the error message on converts // alphanumeric cell name to coordinates. func newCellNameToCoordinatesError(cell string, err error) error { return fmt.Errorf("cannot convert cell %q to coordinates: %v", cell, err) } // newCoordinatesToCellNameError defined the error message on converts [X, Y] // coordinates to alpha-numeric cell name. func newCoordinatesToCellNameError(col, row int) error { return fmt.Errorf("invalid cell reference [%d, %d]", col, row) } // newFieldLengthError defined the error message on receiving the field length // overflow. func newFieldLengthError(name string) error { return fmt.Errorf("field %s must be less than or equal to 255 characters", name) } // newInvalidAutoFilterColumnError defined the error message on receiving the // incorrect index of column. func newInvalidAutoFilterColumnError(col string) error { return fmt.Errorf("incorrect index of column %q", col) } // newInvalidAutoFilterExpError defined the error message on receiving the // incorrect number of tokens in criteria expression. func newInvalidAutoFilterExpError(exp string) error { return fmt.Errorf("incorrect number of tokens in criteria %q", exp) } // newInvalidAutoFilterOperatorError defined the error message on receiving the // incorrect expression operator. func newInvalidAutoFilterOperatorError(op, exp string) error { return fmt.Errorf("the operator %q in expression %q is not valid in relation to Blanks/NonBlanks", op, exp) } // newInvalidCellNameError defined the error message on receiving the invalid // cell name. func newInvalidCellNameError(cell string) error { return fmt.Errorf("invalid cell name %q", cell) } // newInvalidColumnNameError defined the error message on receiving the // invalid column name. func newInvalidColumnNameError(col string) error { return fmt.Errorf("invalid column name %q", col) } // newInvalidExcelDateError defined the error message on receiving the data // with negative values. func newInvalidExcelDateError(dateValue float64) error { return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue) } // newInvalidLinkTypeError defined the error message on receiving the invalid // hyper link type. func newInvalidLinkTypeError(linkType string) error { return fmt.Errorf("invalid link type %q", linkType) } // newInvalidNameError defined the error message on receiving the invalid // defined name or table name. func newInvalidNameError(name string) error { return fmt.Errorf("invalid name %q, the name should be starts with a letter or underscore, can not include a space or character, and can not conflict with an existing name in the workbook", name) } // newInvalidOptionalValue defined the error message on receiving the invalid // optional value. func newInvalidOptionalValue(name, value string, values []string) error { return fmt.Errorf("invalid %s value %q, acceptable value should be one of %s", name, value, strings.Join(values, ", ")) } // newInvalidRowNumberError defined the error message on receiving the invalid // row number. func newInvalidRowNumberError(row int) error { return fmt.Errorf("invalid row number %d", row) } // newInvalidSlicerNameError defined the error message on receiving the invalid // slicer name. func newInvalidSlicerNameError(name string) error { return fmt.Errorf("invalid slicer name %q", name) } // newInvalidStyleID defined the error message on receiving the invalid style // ID. func newInvalidStyleID(styleID int) error { return fmt.Errorf("invalid style ID %d", styleID) } // newNoExistSlicerError defined the error message on receiving the non existing // slicer name. func newNoExistSlicerError(name string) error { return fmt.Errorf("slicer %s does not exist", name) } // newNoExistTableError defined the error message on receiving the non existing // table name. func newNoExistTableError(name string) error { return fmt.Errorf("table %s does not exist", name) } // newNotWorksheetError defined the error message on receiving a sheet which // not a worksheet. func newNotWorksheetError(name string) error { return fmt.Errorf("sheet %s is not a worksheet", name) } // newPivotTableColFieldsError defined the error message on same data field // appears both in the pivot table column fields and filter fields. func newPivotTableColFieldsError(data []string) error { return fmt.Errorf("data fields %s appear both in the pivot table column fields and filter fields", strings.Join(data, ", ")) } // newPivotTableRowFieldsError defined the error message on same data field // appears both in the pivot table row fields and filter fields. func newPivotTableRowFieldsError(data []string) error { return fmt.Errorf("data fields %s appear both in the pivot table row fields and filter fields", strings.Join(data, ", ")) } // newPivotTableDataRangeError defined the error message on receiving the // invalid pivot table data range. func newPivotTableDataRangeError(msg string) error { return fmt.Errorf("parameter 'DataRange' parsing error: %s", msg) } // newPivotTableRangeError defined the error message on receiving the invalid // pivot table range. func newPivotTableRangeError(msg string) error { return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", msg) } // newStreamSetRowError defined the error message on the stream writer // receiving the non-ascending row number. func newStreamSetRowError(row int) error { return fmt.Errorf("row %d has already been written", row) } // newStreamSetRowOrderError defined the error message on calling the SetRow // function before the order function. func newStreamSetRowOrderError(name string) error { return fmt.Errorf("must call the %s function before the SetRow function", name) } // newUnknownFilterTokenError defined the error message on receiving a unknown // filter operator token. func newUnknownFilterTokenError(token string) error { return fmt.Errorf("unknown operator: %s", token) } // newUnsupportedChartType defined the error message on receiving the chart // type are unsupported. func newUnsupportedChartType(chartType ChartType) error { return fmt.Errorf("unsupported chart type %d", chartType) } // newUnsupportedPivotCacheSourceType defined the error message on receiving the // source type of pivot table cache. func newUnsupportedPivotCacheSourceType(sourceType string) error { return fmt.Errorf("unsupported pivot table cache source type: %s", sourceType) } // newUnzipSizeLimitError defined the error message on unzip size exceeds the // limit. func newUnzipSizeLimitError(unzipSizeLimit int64) error { return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit) } // newViewIdxError defined the error message on receiving a invalid sheet view // index. func newViewIdxError(viewIndex int) error { return fmt.Errorf("view index %d out of range", viewIndex) } ================================================ FILE: errors_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" ) func TestNewInvalidColNameError(t *testing.T) { assert.EqualError(t, newInvalidColumnNameError("A"), "invalid column name \"A\"") assert.EqualError(t, newInvalidColumnNameError(""), "invalid column name \"\"") } func TestNewInvalidRowNumberError(t *testing.T) { assert.EqualError(t, newInvalidRowNumberError(0), "invalid row number 0") } func TestNewInvalidCellNameError(t *testing.T) { assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"") assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"") } func TestNewInvalidExcelDateError(t *testing.T) { assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported") } ================================================ FILE: excelize.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. // // See https://xuri.me/excelize for more information about this package. package excelize import ( "archive/zip" "bytes" "encoding/xml" "io" "io/fs" "os" "path/filepath" "strconv" "strings" "sync" "golang.org/x/net/html/charset" ) // File define a populated spreadsheet file struct. type File struct { mu sync.Mutex checked sync.Map formulaChecked bool zip64Entries []string options *Options sharedStringItem [][]uint sharedStringsMap map[string]int sharedStringTemp *os.File sheetMap map[string]string streams map[string]*StreamWriter tempFiles sync.Map xmlAttr sync.Map calcCache sync.Map formulaArgCache sync.Map CalcChain *xlsxCalcChain CharsetReader func(charset string, input io.Reader) (rdr io.Reader, err error) Comments map[string]*xlsxComments ContentTypes *xlsxTypes DecodeVMLDrawing map[string]*decodeVmlDrawing DecodeCellImages *decodeCellImages Drawings sync.Map Path string Pkg sync.Map Relationships sync.Map SharedStrings *xlsxSST Sheet sync.Map SheetCount int Styles *xlsxStyleSheet Theme *decodeTheme VMLDrawing map[string]*vmlDrawing VolatileDeps *xlsxVolTypes WorkBook *xlsxWorkbook ZipWriter func(io.Writer) ZipWriter } // ZipWriter defines an interface for writing files to a ZIP archive. It // provides methods to create new files within the archive, add files from a // filesystem, and close the archive when writing is complete. type ZipWriter interface { Create(name string) (io.Writer, error) AddFS(fsys fs.FS) error Close() error } // Options define the options for opening and reading the spreadsheet. // // MaxCalcIterations specifies the maximum iterations for iterative // calculation, the default value is 0. // // Password specifies the password of the spreadsheet in plain text. // // RawCellValue specifies if apply the number format for the cell value or get // the raw value. // // UnzipSizeLimit specifies to unzip size limit in bytes on open the // spreadsheet, this value should be greater than or equal to // UnzipXMLSizeLimit, the default size limit is 16GB. // // UnzipXMLSizeLimit specifies the memory limit on unzipping worksheet and // shared string table in bytes, worksheet XML will be extracted to system // temporary directory when the file size is over this value, this value // should be less than or equal to UnzipSizeLimit, the default value is // 16MB. // // TmpDir specifies the temporary directory for creating temporary files, if the // value is empty, the system default temporary directory will be used. // // ShortDatePattern specifies the short date number format code. In the // spreadsheet applications, date formats display date and time serial numbers // as date values. Date formats that begin with an asterisk (*) respond to // changes in regional date and time settings that are specified for the // operating system. Formats without an asterisk are not affected by operating // system settings. The ShortDatePattern used for specifies apply date formats // that begin with an asterisk. // // LongDatePattern specifies the long date number format code. // // LongTimePattern specifies the long time number format code. // // CultureInfo specifies the country code for applying built-in language number // format code these effect by the system's local language settings. type Options struct { MaxCalcIterations uint Password string RawCellValue bool UnzipSizeLimit int64 UnzipXMLSizeLimit int64 TmpDir string ShortDatePattern string LongDatePattern string LongTimePattern string CultureInfo CultureName } // OpenFile take the name of a spreadsheet file and returns a populated // spreadsheet file struct for it. For example, open spreadsheet with // password protection: // // f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"}) // // Close the file by Close function after opening the spreadsheet. func OpenFile(filename string, opts ...Options) (*File, error) { file, err := os.Open(filepath.Clean(filename)) if err != nil { return nil, err } f, err := OpenReader(file, opts...) if err != nil { if closeErr := file.Close(); closeErr != nil { return f, closeErr } return f, err } f.Path = filename return f, file.Close() } // newFile is object builder func newFile() *File { return &File{ options: &Options{UnzipSizeLimit: UnzipSizeLimit, UnzipXMLSizeLimit: StreamChunkSize}, xmlAttr: sync.Map{}, checked: sync.Map{}, sheetMap: make(map[string]string), tempFiles: sync.Map{}, Comments: make(map[string]*xlsxComments), Drawings: sync.Map{}, sharedStringsMap: make(map[string]int), Sheet: sync.Map{}, DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), VMLDrawing: make(map[string]*vmlDrawing), Relationships: sync.Map{}, CharsetReader: charset.NewReaderLabel, ZipWriter: func(w io.Writer) ZipWriter { return zip.NewWriter(w) }, } } // checkOpenReaderOptions check and validate options field value for open // reader. func (f *File) checkOpenReaderOptions() error { if f.options.UnzipSizeLimit == 0 { f.options.UnzipSizeLimit = UnzipSizeLimit if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit { f.options.UnzipSizeLimit = f.options.UnzipXMLSizeLimit } } if f.options.UnzipXMLSizeLimit == 0 { f.options.UnzipXMLSizeLimit = StreamChunkSize if f.options.UnzipSizeLimit < f.options.UnzipXMLSizeLimit { f.options.UnzipXMLSizeLimit = f.options.UnzipSizeLimit } } if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit { return ErrOptionsUnzipSizeLimit } return f.checkDateTimePattern() } // OpenReader read data stream from io.Reader and return a populated // spreadsheet file. func OpenReader(r io.Reader, opts ...Options) (*File, error) { b, err := io.ReadAll(r) if err != nil { return nil, err } f := newFile() f.options = f.getOptions(opts...) if err = f.checkOpenReaderOptions(); err != nil { return nil, err } if bytes.Contains(b, oleIdentifier) { if b, err = Decrypt(b, f.options); err != nil { return nil, ErrWorkbookFileFormat } } zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) if err != nil { if len(f.options.Password) > 0 { return nil, ErrWorkbookPassword } return nil, err } file, sheetCount, err := f.ReadZipReader(zr) if err != nil { return nil, err } f.SheetCount = sheetCount for k, v := range file { f.Pkg.Store(k, v) } if f.CalcChain, err = f.calcChainReader(); err != nil { return f, err } if f.sheetMap, err = f.getSheetMap(); err != nil { return f, err } if f.Styles, err = f.stylesReader(); err != nil { return f, err } f.Theme, err = f.themeReader() return f, err } // getOptions provides a function to parse the optional settings for open // and reading spreadsheet. func (f *File) getOptions(opts ...Options) *Options { options := f.options for _, opt := range opts { options = &opt } return options } // CharsetTranscoder set user defined codepage transcoder function for open // workbook from non UTF-8 encoding. func (f *File) CharsetTranscoder(fn func(charset string, input io.Reader) (rdr io.Reader, err error)) *File { f.CharsetReader = fn return f } // SetZipWriter set user defined zip writer function for saving the workbook. func (f *File) SetZipWriter(fn func(io.Writer) ZipWriter) *File { f.ZipWriter = fn; return f } // Creates new XML decoder with charset reader. func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) { ret = xml.NewDecoder(rdr) ret.CharsetReader = f.CharsetReader return } // setDefaultTimeStyle provides a function to set default numbers format for // time.Time type cell value by given worksheet name, cell reference and // number format code. func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error { styleIdx, err := f.GetCellStyle(sheet, cell) if err != nil { return err } if styleIdx == 0 { styleIdx, _ = f.NewStyle(&Style{NumFmt: format}) } else { style, _ := f.GetStyle(styleIdx) style.NumFmt = format styleIdx, _ = f.NewStyle(style) } return f.SetCellStyle(sheet, cell, cell, styleIdx) } // workSheetReader provides a function to get the pointer to the structure // after deserialization by given worksheet name. func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) { var ( name string ok bool ) if err = checkSheetName(sheet); err != nil { return } if name, ok = f.getSheetXMLPath(sheet); !ok { err = ErrSheetNotExist{sheet} return } if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { ws = worksheet.(*xlsxWorksheet) return } for _, sheetType := range []string{"xl/chartsheets", "xl/dialogsheet", "xl/macrosheet"} { if strings.HasPrefix(name, sheetType) { err = newNotWorksheetError(sheet) return } } ws = new(xlsxWorksheet) if attrs, ok := f.xmlAttr.Load(name); !ok { d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))) if attrs == nil { attrs = []xml.Attr{} } attrs = append(attrs.([]xml.Attr), getRootElement(d)...) f.xmlAttr.Store(name, attrs) } if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))). Decode(ws); err != nil && err != io.EOF { return } err = nil if _, ok = f.checked.Load(name); !ok { ws.checkSheet() if err = ws.checkRow(); err != nil { return } f.checked.Store(name, true) } f.Sheet.Store(name, ws) return } // checkSheet provides a function to fill each row element and make that is // continuous in a worksheet of XML. func (ws *xlsxWorksheet) checkSheet() { var ( row int r0Rows []xlsxRow lastRowNum = func(r xlsxRow) int { var num int for _, cell := range r.C { if _, row, err := CellNameToCoordinates(cell.R); err == nil { if row > num { num = row } } } return num } ) for i := 0; i < len(ws.SheetData.Row); i++ { r := ws.SheetData.Row[i] if r.R == 0 || r.R == row { num := lastRowNum(r) if num > row { row = num } if num == 0 { row++ } r.R = row r0Rows = append(r0Rows, r) ws.SheetData.Row = append(ws.SheetData.Row[:i], ws.SheetData.Row[i+1:]...) i-- continue } if r.R != 0 && r.R > row { row = r.R } } sheetData := xlsxSheetData{Row: make([]xlsxRow, row)} row = 0 for _, r := range ws.SheetData.Row { if r.R != 0 { sheetData.Row[r.R-1] = r row = r.R } } for _, r0Row := range r0Rows { sheetData.Row[r0Row.R-1].R = r0Row.R ws.checkSheetR0(&sheetData, &r0Row, true) } for i := 1; i <= row; i++ { sheetData.Row[i-1].R = i ws.checkSheetR0(&sheetData, &sheetData.Row[i-1], false) } } // checkSheetR0 handle the row element with r="0" attribute, cells in this row // could be disorderly, the cell in this row can be used as the value of // which cell is empty in the normal rows. func (ws *xlsxWorksheet) checkSheetR0(sheetData *xlsxSheetData, rowData *xlsxRow, r0 bool) { checkRow := func(col, row int, r0 bool, cell xlsxC) { rowIdx := row - 1 columns, colIdx := len(sheetData.Row[rowIdx].C), col-1 for c := columns; c < col; c++ { sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{}) } if !sheetData.Row[rowIdx].C[colIdx].hasValue() { sheetData.Row[rowIdx].C[colIdx] = cell } if r0 { sheetData.Row[rowIdx].C[colIdx] = cell } } var err error for i, cell := range rowData.C { col, row := i+1, rowData.R if cell.R == "" { checkRow(col, row, r0, cell) continue } if col, row, err = CellNameToCoordinates(cell.R); err == nil && r0 { checkRow(col, row, r0, cell) } } ws.SheetData = *sheetData } // setRels provides a function to set relationships by given relationship ID, // XML path, relationship type, target and target mode. func (f *File) setRels(rID, relPath, relType, target, targetMode string) int { rels, _ := f.relsReader(relPath) if rels == nil || rID == "" { return f.addRels(relPath, relType, target, targetMode) } rels.mu.Lock() defer rels.mu.Unlock() var ID int for i, rel := range rels.Relationships { if rel.ID == rID { rels.Relationships[i].Type = relType rels.Relationships[i].Target = target rels.Relationships[i].TargetMode = targetMode ID, _ = strconv.Atoi(strings.TrimPrefix(rID, "rId")) break } } return ID } // addRels provides a function to add relationships by given XML path, // relationship type, target and target mode. func (f *File) addRels(relPath, relType, target, targetMode string) int { uniqPart := map[string]string{ SourceRelationshipCustomProperties: "/docProps/custom.xml", SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", } rels, _ := f.relsReader(relPath) if rels == nil { rels = &xlsxRelationships{} } rels.mu.Lock() defer rels.mu.Unlock() var rID int for idx, rel := range rels.Relationships { ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) if ID > rID { rID = ID } if relType == rel.Type { if partName, ok := uniqPart[rel.Type]; ok { rels.Relationships[idx].Target = partName return rID } } } rID++ var ID bytes.Buffer ID.WriteString("rId") ID.WriteString(strconv.Itoa(rID)) rels.Relationships = append(rels.Relationships, xlsxRelationship{ ID: ID.String(), Type: relType, Target: target, TargetMode: targetMode, }) f.Relationships.Store(relPath, rels) return rID } // UpdateLinkedValue fix linked values within a spreadsheet are not updating in // Office Excel application. This function will be remove value tag when met a // cell have a linked value. Reference // https://learn.microsoft.com/en-us/archive/msdn-technet-forums/e16bae1f-6a2c-4325-8013-e989a3479066 // // Notice: after opening generated workbook, Excel will update the linked value // and generate a new value and will prompt to save the file or not. // // For example: // // // // SUM(Sheet2!D2,Sheet2!D11) // 100 // // // // to // // // // SUM(Sheet2!D2,Sheet2!D11) // // func (f *File) UpdateLinkedValue() error { wb, err := f.workbookReader() if err != nil { return err } // recalculate formulas wb.CalcPr = nil for _, name := range f.GetSheetList() { ws, err := f.workSheetReader(name) if err != nil { if err.Error() == newNotWorksheetError(name).Error() { continue } return err } for indexR := range ws.SheetData.Row { for indexC, col := range ws.SheetData.Row[indexR].C { if col.F != nil && col.V != "" { ws.SheetData.Row[indexR].C[indexC].V = "" ws.SheetData.Row[indexR].C[indexC].T = "" } } } } return nil } // AddVBAProject provides the method to add vbaProject.bin file which contains // functions and/or macros. The file extension should be XLSM or XLTM. For // example: // // codeName := "Sheet1" // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // CodeName: &codeName, // }); err != nil { // fmt.Println(err) // return // } // file, err := os.ReadFile("vbaProject.bin") // if err != nil { // fmt.Println(err) // return // } // if err := f.AddVBAProject(file); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("macros.xlsm"); err != nil { // fmt.Println(err) // return // } func (f *File) AddVBAProject(file []byte) error { var err error // Check vbaProject.bin exists first. if !bytes.Contains(file, oleIdentifier) { return ErrAddVBAProject } rels, err := f.relsReader(f.getWorkbookRelsPath()) if err != nil { return err } rels.mu.Lock() defer rels.mu.Unlock() var rID int var ok bool for _, rel := range rels.Relationships { if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject { ok = true continue } t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) if t > rID { rID = t } } rID++ if !ok { rels.Relationships = append(rels.Relationships, xlsxRelationship{ ID: "rId" + strconv.Itoa(rID), Target: "vbaProject.bin", Type: SourceRelationshipVBAProject, }) } f.Pkg.Store("xl/vbaProject.bin", file) return err } // setContentTypePartProjectExtensions provides a function to set the content // type for relationship parts and the main document part. func (f *File) setContentTypePartProjectExtensions(contentType string) error { var ok bool content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for _, v := range content.Defaults { if v.Extension == "bin" { ok = true } } for idx, o := range content.Overrides { if o.PartName == "/xl/workbook.xml" { content.Overrides[idx].ContentType = contentType } } if !ok { content.Defaults = append(content.Defaults, xlsxDefault{ Extension: "bin", ContentType: ContentTypeVBA, }) } return err } // metadataReader provides a function to get the pointer to the structure // after deserialization of xl/metadata.xml. func (f *File) metadataReader() (*xlsxMetadata, error) { var metaData xlsxMetadata if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))). Decode(&metaData); err != nil && err != io.EOF { return &metaData, err } return &metaData, nil } // richValueReader provides a function to get the pointer to the structure after // deserialization of xl/richData/richvalue.xml. func (f *File) richValueReader() (*xlsxRichValueData, error) { var richValue xlsxRichValueData if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValue)))). Decode(&richValue); err != nil && err != io.EOF { return &richValue, err } return &richValue, nil } // richValueRelReader provides a function to get the pointer to the structure // after deserialization of xl/richData/richValueRel.xml. func (f *File) richValueRelReader() (*xlsxRichValueRels, error) { var richValueRels xlsxRichValueRels if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueRel)))). Decode(&richValueRels); err != nil && err != io.EOF { return &richValueRels, err } return &richValueRels, nil } // richValueStructuresReader provides a function to get the pointer to the structure after // deserialization of xl/richData/rdrichvaluestructure.xml. func (f *File) richValueStructuresReader() (*xlsxRichValueStructures, error) { var richValueStructures xlsxRichValueStructures if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueStructure)))). Decode(&richValueStructures); err != nil && err != io.EOF { return &richValueStructures, err } return &richValueStructures, nil } // richValueWebImageReader provides a function to get the pointer to the // structure after deserialization of xl/richData/rdRichValueWebImage.xml. func (f *File) richValueWebImageReader() (*xlsxWebImagesSupportingRichData, error) { var richValueWebImages xlsxWebImagesSupportingRichData if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueWebImage)))). Decode(&richValueWebImages); err != nil && err != io.EOF { return &richValueWebImages, err } return &richValueWebImages, nil } // getRichDataRichValueRelRelationships provides a function to get relationships // from xl/richData/_rels/richValueRel.xml.rels by given relationship ID. func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationship { if rels, _ := f.relsReader(defaultXMLRdRichValueRelRels); rels != nil { rels.mu.Lock() defer rels.mu.Unlock() for _, v := range rels.Relationships { if v.ID == rID { return &v } } } return nil } // getRichValueWebImageRelationships provides a function to get relationships // from xl/richData/_rels/rdRichValueWebImage.xml.rels by given relationship ID. func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship { if rels, _ := f.relsReader(defaultXMLRdRichValueWebImageRels); rels != nil { rels.mu.Lock() defer rels.mu.Unlock() for _, v := range rels.Relationships { if v.ID == rID { return &v } } } return nil } ================================================ FILE: excelize_test.go ================================================ package excelize import ( "archive/zip" "bytes" "compress/gzip" "encoding/xml" "fmt" "image/color" _ "image/gif" _ "image/jpeg" _ "image/png" "io" "math" "os" "path/filepath" "strconv" "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" "golang.org/x/net/html/charset" ) func TestOpenFile(t *testing.T) { // Test update the spreadsheet file f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test get all the rows in a not exists worksheet _, err = f.GetRows("Sheet4") assert.EqualError(t, err, "sheet Sheet4 does not exist") // Test get all the rows with invalid sheet name _, err = f.GetRows("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test get all the rows in a worksheet rows, err := f.GetRows("Sheet2") expected := [][]string{ {"Monitor", "", "Brand", "", "inlineStr"}, {"> 23 Inch", "19", "HP", "200"}, {"20-23 Inch", "24", "DELL", "450"}, {"17-20 Inch", "56", "Lenove", "200"}, {"< 17 Inch", "21", "SONY", "510"}, {"", "", "Acer", "315"}, {"", "", "IBM", "127"}, {"", "", "ASUS", "89"}, {"", "", "Apple", "348"}, {"", "", "SAMSUNG", "53"}, {"", "", "Other", "37", "", "", "", "", ""}, } assert.NoError(t, err) assert.Equal(t, expected, rows) assert.NoError(t, f.UpdateLinkedValue()) assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(100.1588, 'f', -1, 32))) assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(-100.1588, 'f', -1, 64))) // Test set cell value with invalid sheet name assert.EqualError(t, f.SetCellDefault("Sheet:1", "A1", ""), ErrSheetNameInvalid.Error()) // Test set cell value with illegal row number assert.EqualError(t, f.SetCellDefault("Sheet2", "A", strconv.FormatFloat(-100.1588, 'f', -1, 64)), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.NoError(t, f.SetCellInt("Sheet2", "A1", 100)) // Test set cell integer value with illegal row number assert.EqualError(t, f.SetCellInt("Sheet2", "A", 100), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test set cell integer value with invalid sheet name assert.EqualError(t, f.SetCellInt("Sheet:1", "A1", 100), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns")) // Test max characters in a cell assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2))) _, err = f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.") assert.EqualError(t, err, ErrSheetNameLength.Error()) // Test set worksheet name with illegal name assert.EqualError(t, f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title."), ErrSheetNameLength.Error()) assert.EqualError(t, f.SetCellInt("Sheet3", "A23", 10), "sheet Sheet3 does not exist") assert.EqualError(t, f.SetCellStr("Sheet3", "b230", "10"), "sheet Sheet3 does not exist") assert.EqualError(t, f.SetCellStr("Sheet10", "b230", "10"), "sheet Sheet10 does not exist") // Test set cell string data type value with invalid sheet name assert.EqualError(t, f.SetCellStr("Sheet:1", "A1", "1"), ErrSheetNameInvalid.Error()) // Test set cell string value with illegal row number assert.EqualError(t, f.SetCellStr("Sheet1", "A", "10"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) f.SetActiveSheet(2) // Test get cell formula with given rows number formula, err := f.GetCellFormula("Sheet1", "B19") assert.NoError(t, err) assert.Equal(t, "SUM(Sheet2!D2,Sheet2!D11)", formula) // Test get cell formula with illegal worksheet name formula, err = f.GetCellFormula("Sheet2", "B20") assert.NoError(t, err) assert.Empty(t, formula) // Test get cell formula with illegal rows number _, err = f.GetCellFormula("Sheet1", "B") assert.EqualError(t, err, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) // Test get shared cell formula _, err = f.GetCellFormula("Sheet2", "H11") assert.NoError(t, err) _, err = f.GetCellFormula("Sheet2", "I11") assert.NoError(t, err) _, err = getSharedFormula(&xlsxWorksheet{}, 0, "") assert.NoError(t, err) // Test read cell value with given illegal rows number _, err = f.GetCellValue("Sheet2", "a-1") assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error()) _, err = f.GetCellValue("Sheet2", "A") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test read cell value with given lowercase column number _, err = f.GetCellValue("Sheet2", "a5") assert.NoError(t, err) _, err = f.GetCellValue("Sheet2", "C11") assert.NoError(t, err) _, err = f.GetCellValue("Sheet2", "D11") assert.NoError(t, err) _, err = f.GetCellValue("Sheet2", "D12") assert.NoError(t, err) // Test SetCellValue function assert.NoError(t, f.SetCellValue("Sheet2", "F1", " Hello")) assert.NoError(t, f.SetCellValue("Sheet2", "G1", []byte("World"))) assert.NoError(t, f.SetCellValue("Sheet2", "F2", 42)) assert.NoError(t, f.SetCellValue("Sheet2", "F3", int8(1<<8/2-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F4", int16(1<<16/2-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F5", int32(1<<32/2-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F6", int64(1<<32/2-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F7", float32(42.65418))) assert.NoError(t, f.SetCellValue("Sheet2", "F8", -42.65418)) assert.NoError(t, f.SetCellValue("Sheet2", "F9", float32(42))) assert.NoError(t, f.SetCellValue("Sheet2", "F10", float64(42))) assert.NoError(t, f.SetCellValue("Sheet2", "F11", uint(1<<32-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F12", uint8(1<<8-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F13", uint16(1<<16-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F14", uint32(1<<32-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F15", uint64(1<<32-1))) assert.NoError(t, f.SetCellValue("Sheet2", "F16", true)) assert.NoError(t, f.SetCellValue("Sheet2", "F17", complex64(5+10i))) // Test on not exists worksheet assert.EqualError(t, f.SetCellDefault("SheetN", "A1", ""), "sheet SheetN does not exist") assert.EqualError(t, f.SetCellFloat("SheetN", "A1", 42.65418, 2, 32), "sheet SheetN does not exist") assert.EqualError(t, f.SetCellBool("SheetN", "A1", true), "sheet SheetN does not exist") assert.EqualError(t, f.SetCellFormula("SheetN", "A1", ""), "sheet SheetN does not exist") assert.EqualError(t, f.SetCellHyperLink("SheetN", "A1", "Sheet1!A40", "Location"), "sheet SheetN does not exist") // Test boolean write boolTest := []struct { value bool raw bool expected string }{ {false, true, "0"}, {true, true, "1"}, {false, false, "FALSE"}, {true, false, "TRUE"}, } for _, test := range boolTest { assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value)) val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw}) assert.NoError(t, err) assert.Equal(t, test.expected, val) } assert.NoError(t, f.SetCellValue("Sheet2", "G2", nil)) assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now())) assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now().UTC())) assert.EqualError(t, f.SetCellValue("SheetN", "A1", time.Now()), "sheet SheetN does not exist") // 02:46:40 assert.NoError(t, f.SetCellValue("Sheet2", "G5", time.Duration(1e13))) // Test completion column assert.NoError(t, f.SetCellValue("Sheet2", "M2", nil)) // Test read cell value with given cell reference large than exists row _, err = f.GetCellValue("Sheet2", "E231") assert.NoError(t, err) // Test get active worksheet of spreadsheet and get worksheet name of // spreadsheet by given worksheet index f.GetSheetName(f.GetActiveSheetIndex()) // Test get worksheet index of spreadsheet by given worksheet name _, err = f.GetSheetIndex("Sheet1") assert.NoError(t, err) // Test get worksheet name of spreadsheet by given invalid worksheet index f.GetSheetName(4) // Test get worksheet map of workbook f.GetSheetMap() for i := 1; i <= 300; i++ { assert.NoError(t, f.SetCellStr("Sheet2", "c"+strconv.Itoa(i), strconv.Itoa(i))) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOpenFile.xlsx"))) assert.EqualError(t, f.SaveAs(filepath.Join("test", strings.Repeat("c", 199), ".xlsx")), ErrMaxFilePathLength.Error()) assert.NoError(t, f.Close()) } func TestSaveFile(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsb")), ErrWorkbookFileFormat.Error()) for _, ext := range []string{".xlam", ".xlsm", ".xlsx", ".xltm", ".xltx"} { assert.NoError(t, f.SaveAs(filepath.Join("test", fmt.Sprintf("TestSaveFile%s", ext)))) } assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestSaveFile.xlsx")) assert.NoError(t, err) assert.NoError(t, f.Save()) assert.NoError(t, f.Close()) t.Run("for_save_multiple_times", func(t *testing.T) { { f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A20", 20)) assert.NoError(t, f.Save()) assert.NoError(t, f.SetCellValue("Sheet1", "A21", 21)) assert.NoError(t, f.Save()) assert.NoError(t, f.Close()) } { f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx")) assert.NoError(t, err) val, err := f.GetCellValue("Sheet1", "A20") assert.NoError(t, err) assert.Equal(t, "20", val) val, err = f.GetCellValue("Sheet1", "A21") assert.NoError(t, err) assert.Equal(t, "21", val) assert.NoError(t, f.Close()) } }) } func TestSaveAsWrongPath(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test write file to not exist directory assert.Error(t, f.SaveAs(filepath.Join("x", "Book1.xlsx"))) assert.NoError(t, f.Close()) } func TestCharsetTranscoder(t *testing.T) { f := NewFile() f.CharsetTranscoder(charset.NewReaderLabel) } func TestOpenReader(t *testing.T) { _, err := OpenReader(strings.NewReader("")) assert.EqualError(t, err, zip.ErrFormat.Error()) _, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password", UnzipXMLSizeLimit: UnzipSizeLimit + 1}) assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) // Prepare unusual workbook, made the specified internal XML parts missing // or contain unsupported charset preset := func(filePath string, notExist bool) *bytes.Buffer { source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) buf := new(bytes.Buffer) zw := zip.NewWriter(buf) for _, item := range source.File { // The following statements can be simplified as zw.Copy(item) in go1.17 if notExist && item.Name == filePath { continue } writer, err := zw.Create(item.Name) assert.NoError(t, err) readerCloser, err := item.Open() assert.NoError(t, err) _, err = io.Copy(writer, readerCloser) assert.NoError(t, err) } if !notExist { fi, err := zw.Create(filePath) assert.NoError(t, err) _, err = fi.Write(MacintoshCyrillicCharset) assert.NoError(t, err) } assert.NoError(t, zw.Close()) return buf } // Test open workbook with unsupported charset internal XML parts for _, defaultXMLPath := range []string{ defaultXMLPathCalcChain, defaultXMLPathStyles, defaultXMLPathWorkbookRels, } { _, err = OpenReader(preset(defaultXMLPath, false)) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } // Test open workbook without internal XML parts for _, defaultXMLPath := range []string{ defaultXMLPathCalcChain, defaultXMLPathStyles, defaultXMLPathWorkbookRels, } { _, err = OpenReader(preset(defaultXMLPath, true)) assert.NoError(t, err) } // Test open spreadsheet with unzip size limit _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100}) assert.EqualError(t, err, newUnzipSizeLimitError(100).Error()) // Test open password protected spreadsheet created by Microsoft Office Excel 2010 f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"}) assert.NoError(t, err) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "SECRET", val) assert.NoError(t, f.Close()) // Test open password protected spreadsheet created by LibreOffice 7.0.0.3 f, err = OpenFile(filepath.Join("test", "encryptAES.xlsx"), Options{Password: "password"}) assert.NoError(t, err) val, err = f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "SECRET", val) assert.NoError(t, f.Close()) // Test open spreadsheet with invalid options _, err = OpenReader(bytes.NewReader(oleIdentifier), Options{UnzipSizeLimit: 1, UnzipXMLSizeLimit: 2}) assert.EqualError(t, err, ErrOptionsUnzipSizeLimit.Error()) // Test unexpected EOF var b bytes.Buffer w := gzip.NewWriter(&b) defer func() { assert.NoError(t, w.Close()) }() assert.NoError(t, w.Flush()) r, _ := gzip.NewReader(&b) defer func() { assert.EqualError(t, r.Close(), "unexpected EOF") }() _, err = OpenReader(r) assert.EqualError(t, err, "unexpected EOF") _, err = OpenReader(bytes.NewReader([]byte{ 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00, 0x00, 0x21, 0x06, 0x59, 0xc0, 0x12, 0xf3, 0x19, 0xc7, 0x51, 0xd1, 0xc9, 0x31, 0xcb, 0xcc, 0x8a, 0xe1, 0x44, 0xe1, 0x56, 0x20, 0x24, 0x1f, 0xba, 0x09, 0xda, 0x53, 0xd5, 0xef, 0x50, 0x4b, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1f, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x41, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, })) assert.EqualError(t, err, zip.ErrAlgorithm.Error()) } func TestBrokenFile(t *testing.T) { // Test write file with broken file struct f := File{ZipWriter: func(w io.Writer) ZipWriter { return zip.NewWriter(w) }} t.Run("SaveWithoutName", func(t *testing.T) { assert.EqualError(t, f.Save(), "no path defined for file, consider File.WriteTo or File.Write") }) t.Run("SaveAsEmptyStruct", func(t *testing.T) { // Test write file with broken file struct with given path assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx"))) }) t.Run("OpenBadWorkbook", func(t *testing.T) { // Test set active sheet without BookViews and Sheets maps in xl/workbook.xml f3, err := OpenFile(filepath.Join("test", "BadWorkbook.xlsx")) f3.GetActiveSheetIndex() f3.SetActiveSheet(1) assert.NoError(t, err) assert.NoError(t, f3.Close()) }) t.Run("OpenNotExistsFile", func(t *testing.T) { // Test open a spreadsheet file with given illegal path _, err := OpenFile(filepath.Join("test", "NotExistsFile.xlsx")) if assert.Error(t, err) { assert.True(t, os.IsNotExist(err), "Expected os.IsNotExists(err) == true") } }) } func TestNewFile(t *testing.T) { // Test create a spreadsheet file f := NewFile() _, err := f.NewSheet("Sheet1") assert.NoError(t, err) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) assert.NoError(t, f.SetCellInt("Sheet2", "A23", 56)) assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42")) f.SetActiveSheet(0) // Test add picture to sheet with scaling and positioning assert.NoError(t, f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), &GraphicOptions{ScaleX: 0.5, ScaleY: 0.5, Positioning: "absolute"})) // Test add picture to worksheet without options assert.NoError(t, f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewFile.xlsx"))) assert.NoError(t, f.Save()) } func TestSetCellHyperLink(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test set cell hyperlink in a work sheet already have hyperlinks assert.NoError(t, f.SetCellHyperLink("Sheet1", "B19", "https://github.com/xuri/excelize", "External")) // Test add first hyperlink in a work sheet assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/xuri/excelize", "External")) // Test add Location hyperlink in a work sheet assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location")) // Test add Location hyperlink with display & tooltip in a work sheet display, tooltip := "Display value", "Hover text" assert.NoError(t, f.SetCellHyperLink("Sheet2", "D7", "Sheet1!D9", "Location", HyperlinkOpts{ Display: &display, Tooltip: &tooltip, })) cells, err := f.GetHyperLinkCells("Sheet1", "") assert.NoError(t, err) assert.Equal(t, []string{"A22", "B19"}, cells) cells, err = f.GetHyperLinkCells("Sheet2", "") assert.NoError(t, err) assert.Equal(t, []string{"C1", "D6", "D7"}, cells) cells, err = f.GetHyperLinkCells("Sheet2", "External") assert.NoError(t, err) assert.Equal(t, []string{"C1"}, cells) cells, err = f.GetHyperLinkCells("Sheet2", "Location") assert.NoError(t, err) assert.Equal(t, []string{"D6", "D7"}, cells) cells, err = f.GetHyperLinkCells("Sheet2", "None") assert.NoError(t, err) assert.Empty(t, cells) // Test get hyperlink cells on not exists worksheet _, err = f.GetHyperLinkCells("SheetN", "") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get hyperlink cells with invalid link type _, err = f.GetHyperLinkCells("Sheet2", "InvalidType") assert.Equal(t, err, newInvalidLinkTypeError("InvalidType")) // Test set cell hyperlink with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetCellHyperLink("Sheet:1", "A1", "Sheet1!D60", "Location")) assert.Equal(t, newInvalidLinkTypeError(""), f.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", "")) assert.EqualError(t, f.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location"), `invalid cell name ""`) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx"))) assert.NoError(t, f.Close()) f = NewFile() _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)} assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/xuri/excelize", "External"), ErrTotalSheetHyperlinks.Error()) f = NewFile() _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} err = f.SetCellHyperLink("Sheet1", "A1", "https://github.com/xuri/excelize", "External") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test update cell hyperlink f = NewFile() assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com", "External")) assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com/xuri/excelize", "External")) link, target, err := f.GetCellHyperLink("Sheet1", "A1") assert.Equal(t, link, true) assert.Equal(t, "https://github.com/xuri/excelize", target) assert.NoError(t, err) // Test remove hyperlink for a cell f = NewFile() assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location")) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A1:D4" assert.NoError(t, f.SetCellHyperLink("Sheet1", "B2", "", "None")) // Test remove hyperlink for a cell with invalid cell reference assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location")) ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A:A" assert.Error(t, f.SetCellHyperLink("Sheet1", "B2", "", "None"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) // Test get hyperlink cells on worksheet without hyperlinks f = NewFile() cells, err = f.GetHyperLinkCells("Sheet1", "") assert.NoError(t, err) assert.Empty(t, cells) } func TestGetCellHyperLink(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) _, _, err = f.GetCellHyperLink("Sheet1", "") assert.EqualError(t, err, `invalid cell name ""`) link, target, err := f.GetCellHyperLink("Sheet1", "A22") assert.NoError(t, err) assert.Equal(t, link, true) assert.Equal(t, target, "https://github.com/xuri/excelize") link, target, err = f.GetCellHyperLink("Sheet2", "D6") assert.NoError(t, err) assert.Equal(t, link, false) assert.Equal(t, target, "") link, target, err = f.GetCellHyperLink("Sheet3", "H3") assert.EqualError(t, err, "sheet Sheet3 does not exist") assert.Equal(t, link, false) assert.Equal(t, target, "") assert.NoError(t, f.Close()) f = NewFile() _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{ Hyperlink: []xlsxHyperlink{{Ref: "A1"}}, } link, target, err = f.GetCellHyperLink("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, link, true) assert.Equal(t, target, "") ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: []xlsxHyperlink{{Ref: "A:A"}}} link, target, err = f.GetCellHyperLink("Sheet1", "A1") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.Equal(t, link, false) assert.Equal(t, target, "") // Test get cell hyperlink with invalid sheet name _, _, err = f.GetCellHyperLink("Sheet:1", "A1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) } func TestSetSheetBackground(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))) assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackground.xlsx"))) assert.NoError(t, f.Close()) } func TestSetSheetBackgroundErrors(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) err = f.SetSheetBackground("Sheet2", filepath.Join("test", "not_exists", "not_exists.png")) if assert.Error(t, err) { assert.True(t, os.IsNotExist(err), "Expected os.IsNotExists(err) == true") } err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx")) assert.EqualError(t, err, ErrImgExt.Error()) // Test set sheet background on not exist worksheet err = f.SetSheetBackground("SheetN", filepath.Join("test", "images", "background.jpg")) assert.EqualError(t, err, "sheet SheetN does not exist") // Test set sheet background with invalid sheet name assert.EqualError(t, f.SetSheetBackground("Sheet:1", filepath.Join("test", "images", "background.jpg")), ErrSheetNameInvalid.Error()) assert.NoError(t, f.Close()) // Test set sheet background with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8") } // TestWriteArrayFormula tests the extended options of SetCellFormula by writing // an array function to a workbook. In the resulting file, the lines 2 and 3 as // well as 4 and 5 should have matching contents func TestWriteArrayFormula(t *testing.T) { cell := func(col, row int) string { c, err := CoordinatesToCellName(col, row) if err != nil { t.Fatal(err) } return c } f := NewFile() sample := []string{"Sample 1", "Sample 2", "Sample 3"} values := []int{1855, 1709, 1462, 1115, 1524, 625, 773, 126, 1027, 1696, 1078, 1917, 1109, 1753, 1884, 659, 994, 1911, 1925, 899, 196, 244, 1488, 1056, 1986, 66, 784, 725, 767, 1722, 1541, 1026, 1455, 264, 1538, 877, 1581, 1098, 383, 762, 237, 493, 29, 1923, 474, 430, 585, 688, 308, 200, 1259, 622, 798, 1048, 996, 601, 582, 332, 377, 805, 250, 1860, 1360, 840, 911, 1346, 1651, 1651, 665, 584, 1057, 1145, 925, 1752, 202, 149, 1917, 1398, 1894, 818, 714, 624, 1085, 1566, 635, 78, 313, 1686, 1820, 494, 614, 1913, 271, 1016, 338, 1301, 489, 1733, 1483, 1141} assoc := []int{2, 0, 0, 0, 0, 1, 1, 0, 0, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 1, 0, 2, 0, 2, 1, 2, 2, 2, 1, 0, 1, 0, 1, 1, 2, 0, 2, 1, 0, 2, 1, 0, 1, 0, 0, 2, 0, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 1, 1, 0, 1, 0, 2, 0, 0, 1, 2, 1, 0, 1, 0, 0, 2, 1, 1, 2, 0, 2, 1, 0, 2, 2, 2, 1, 0, 0, 1, 1, 1, 2, 0, 2, 0, 1, 1} if len(values) != len(assoc) { t.Fatal("values and assoc must be of same length") } // Average calculates the average of the n-th sample (0 <= n < len(sample)). average := func(n int) int { sum := 0 count := 0 for i := 0; i != len(values); i++ { if assoc[i] == n { sum += values[i] count++ } } return int(math.Round(float64(sum) / float64(count))) } // Stdev calculates the standard deviation of the n-th sample (0 <= n < len(sample)). stdev := func(n int) int { avg := average(n) sum := 0 count := 0 for i := 0; i != len(values); i++ { if assoc[i] == n { sum += (values[i] - avg) * (values[i] - avg) count++ } } return int(math.Round(math.Sqrt(float64(sum) / float64(count)))) } // Line 2 contains the results of AVERAGEIF assert.NoError(t, f.SetCellStr("Sheet1", "A2", "Average")) // Line 3 contains the average that was calculated in Go assert.NoError(t, f.SetCellStr("Sheet1", "A3", "Average (calculated)")) // Line 4 contains the results of the array function that calculates the standard deviation assert.NoError(t, f.SetCellStr("Sheet1", "A4", "Std. deviation")) // Line 5 contains the standard deviations calculated in Go assert.NoError(t, f.SetCellStr("Sheet1", "A5", "Std. deviation (calculated)")) assert.NoError(t, f.SetCellStr("Sheet1", "B1", sample[0])) assert.NoError(t, f.SetCellStr("Sheet1", "C1", sample[1])) assert.NoError(t, f.SetCellStr("Sheet1", "D1", sample[2])) firstResLine := 8 assert.NoError(t, f.SetCellStr("Sheet1", cell(1, firstResLine-1), "Result Values")) assert.NoError(t, f.SetCellStr("Sheet1", cell(2, firstResLine-1), "Sample")) for i := 0; i != len(values); i++ { valCell := cell(1, i+firstResLine) assocCell := cell(2, i+firstResLine) assert.NoError(t, f.SetCellInt("Sheet1", valCell, int64(values[i]))) assert.NoError(t, f.SetCellStr("Sheet1", assocCell, sample[assoc[i]])) } valRange := fmt.Sprintf("$A$%d:$A$%d", firstResLine, len(values)+firstResLine-1) assocRange := fmt.Sprintf("$B$%d:$B$%d", firstResLine, len(values)+firstResLine-1) for i := 0; i != len(sample); i++ { nameCell := cell(i+2, 1) avgCell := cell(i+2, 2) calcAvgCell := cell(i+2, 3) stdevCell := cell(i+2, 4) calcStdevCell := cell(i+2, 5) assert.NoError(t, f.SetCellInt("Sheet1", calcAvgCell, int64(average(i)))) assert.NoError(t, f.SetCellInt("Sheet1", calcStdevCell, int64(stdev(i)))) // Average can be done with AVERAGEIF assert.NoError(t, f.SetCellFormula("Sheet1", avgCell, fmt.Sprintf("ROUND(AVERAGEIF(%s,%s,%s),0)", assocRange, nameCell, valRange))) ref := stdevCell + ":" + stdevCell arr := STCellFormulaTypeArray // Use an array formula for standard deviation assert.NoError(t, f.SetCellFormula("Sheet1", stdevCell, fmt.Sprintf("ROUND(STDEVP(IF(%s=%s,%s)),0)", assocRange, nameCell, valRange), FormulaOpts{}, FormulaOpts{Type: &arr}, FormulaOpts{Ref: &ref})) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestWriteArrayFormula.xlsx"))) } func TestSetCellStyleAlignment(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) var style int style, err = f.NewStyle(&Style{Alignment: &Alignment{Horizontal: "center", Indent: 1, JustifyLastLine: true, ReadingOrder: 0, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 45, Vertical: "top", WrapText: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style)) // Test set cell style with given illegal rows number assert.EqualError(t, f.SetCellStyle("Sheet1", "A", "A22", style), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.SetCellStyle("Sheet1", "A22", "A", style), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test set cell style with invalid sheet name assert.EqualError(t, f.SetCellStyle("Sheet:1", "A1", "A2", style), ErrSheetNameInvalid.Error()) // Test get cell style with given illegal rows number index, err := f.GetCellStyle("Sheet1", "A") assert.Equal(t, 0, index) assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test get cell style with invalid sheet name _, err = f.GetCellStyle("Sheet:1", "A1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleAlignment.xlsx"))) } func TestSetCellStyleBorder(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) var style int // Test set border on overlapping range with vertical variants shading styles gradient fill style, err = f.NewStyle(&Style{ Border: []Border{ {Type: "left", Color: "0000FF", Style: 3}, {Type: "top", Color: "00FF00", Style: 4}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "diagonalDown", Color: "A020F0", Style: 7}, {Type: "diagonalUp", Color: "A020F0", Style: 8}, }, }) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "J21", "L25", style)) style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 4}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) // Test set border and solid style pattern fill for a single cell style, err = f.NewStyle(&Style{ Border: []Border{ { Type: "left", Color: "0000FF", Style: 8, }, { Type: "top", Color: "00FF00", Style: 9, }, { Type: "bottom", Color: "FFFF00", Style: 10, }, { Type: "right", Color: "FF0000", Style: 11, }, { Type: "diagonalDown", Color: "A020F0", Style: 12, }, { Type: "diagonalUp", Color: "A020F0", Style: 13, }, }, Fill: Fill{ Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1, }, }) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleBorder.xlsx"))) } func TestSetCellStyleBorderErrors(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) // Set border with invalid style index number _, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: -1}, {Type: "top", Color: "00FF00", Style: 14}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "diagonalDown", Color: "A020F0", Style: 9}, {Type: "diagonalUp", Color: "A020F0", Style: 8}}}) assert.NoError(t, err) } func TestSetCellStyleNumberFormat(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) // Test only set fill and number format for a cell col := []string{"L", "M", "N", "O", "P"} idxTbl := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49} value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"} expected := [][]string{ {"37947.75", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947 3/4", "37947 3/4 ", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", " 37,948 ", " $37,948 ", " 37,947.75 ", " $37,947.75 ", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"}, {"-37947.75", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947 3/4", "-37947 3/4 ", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", " (37,948)", " $(37,948)", " (37,947.75)", " $(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"}, {"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0 ", "0 ", "01-00-00", "0-Jan-00", "0-Jan", "Jan-00", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "1/0/00 00:10", "0 ", "0 ", "0.01 ", "0.01 ", " 0 ", " $0 ", " 0.01 ", " $0.01 ", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"}, {"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2 1/9", "2 1/10", "01-02-00", "2-Jan-00", "2-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/2/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", " 2 ", " $2 ", " 2.10 ", " $2.10 ", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"}, {"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", " String ", " String ", " String ", " String ", "String", "String", "String", "String", "String"}, } for c, v := range value { for r, idx := range idxTbl { cell := col[c] + strconv.Itoa(r+1) var val float64 val, err = strconv.ParseFloat(v, 64) if err != nil { assert.NoError(t, f.SetCellValue("Sheet2", cell, v)) } else { assert.NoError(t, f.SetCellValue("Sheet2", cell, val)) } style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 5}, NumFmt: idx}) if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.SetCellStyle("Sheet2", cell, cell, style)) cellValue, err := f.GetCellValue("Sheet2", cell) assert.Equal(t, expected[c][r], cellValue, fmt.Sprintf("Sheet2!%s value: %s, number format: %s c: %d r: %d", cell, value[c], builtInNumFmt[idx], c, r)) assert.NoError(t, err) } } var style int style, err = f.NewStyle(&Style{NumFmt: -1}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx"))) // Test get cell value with built-in number format code 22 with custom short date pattern f = NewFile(Options{ShortDatePattern: "yyyy-m-dd"}) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 45074.625694444447)) style, err = f.NewStyle(&Style{NumFmt: 22}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) cellValue, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "2023-5-28 15:01", cellValue) } func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { t.Run("TestBook3", func(t *testing.T) { f, err := prepareTestBook3() assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3)) var style int style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: intPtr(-1)}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: intPtr(31), NegRed: true}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook3.xlsx"))) }) t.Run("TestBook4", func(t *testing.T) { f, err := prepareTestBook4() assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) _, err = f.NewStyle(&Style{NumFmt: 26}) assert.NoError(t, err) style, err := f.NewStyle(&Style{NumFmt: 27}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = f.NewStyle(&Style{NumFmt: 31}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) style, err = f.NewStyle(&Style{NumFmt: 71}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx"))) }) } func TestSetCellStyleLangNumberFormat(t *testing.T) { rawCellValues := make([][]string, 42) for i := 0; i < 42; i++ { rawCellValues[i] = []string{"45162"} } for lang, expected := range map[CultureName][][]string{ CultureNameUnknown: rawCellValues, CultureNameEnUS: {{"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"45162"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0時00分"}, {"0時00分00秒"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"08-24-56"}, {"4356년 08월 24일"}, {"0시 00분"}, {"0시 00분 00초"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0时00分"}, {"0时00分00秒"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"00時00分"}, {"00時00分00秒"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, } { f, err := prepareTestBook5(Options{CultureInfo: lang}) assert.NoError(t, err) rows, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, rows) assert.NoError(t, f.Close()) } // Test apply language number format code with date and time pattern for lang, expected := range map[CultureName][][]string{ CultureNameEnUS: {{"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"45162"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"4356-8-24"}, {"4356년 08월 24일"}, {"00:00:00"}, {"00:00:00"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}, } { f, err := prepareTestBook5(Options{CultureInfo: lang, ShortDatePattern: "yyyy-M-d", LongTimePattern: "hh:mm:ss"}) assert.NoError(t, err) rows, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, rows) assert.NoError(t, f.Close()) } // Test open workbook with invalid date and time pattern options _, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongDatePattern: "0.00"}) assert.Equal(t, ErrUnsupportedNumberFormat, err) _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongTimePattern: "0.00"}) assert.Equal(t, ErrUnsupportedNumberFormat, err) _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{ShortDatePattern: "0.00"}) assert.Equal(t, ErrUnsupportedNumberFormat, err) } func TestSetCellStyleCustomNumberFormat(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) customNumFmt := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" style, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt, Font: &Font{Color: "9A0511"}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) customNumFmt = "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@" _, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx"))) } func TestSetCellStyleFill(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) var style int // Test set fill for cell with invalid parameter style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 6}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{}, Shading: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx"))) } func TestSetCellStyleFont(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) var style int style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "777777", Underline: "single"}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style)) style, err = f.NewStyle(&Style{Font: &Font{Italic: true, Underline: "double"}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style)) style, err = f.NewStyle(&Style{Font: &Font{Bold: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style)) style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Family: "", Size: 0, Color: "", Underline: ""}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style)) style, err = f.NewStyle(&Style{Font: &Font{Color: "777777", Strike: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFont.xlsx"))) } func TestSetCellStyleProtection(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) var style int style, err = f.NewStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style)) err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx")) assert.NoError(t, err) } func TestSetDeleteSheet(t *testing.T) { t.Run("TestBook3", func(t *testing.T) { f, err := prepareTestBook3() assert.NoError(t, err) assert.NoError(t, f.DeleteSheet("Sheet3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx"))) }) t.Run("TestBook4", func(t *testing.T) { f, err := prepareTestBook4() assert.NoError(t, err) assert.NoError(t, f.DeleteSheet("Sheet1")) assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A1", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx"))) }) } func TestSheetVisibility(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) assert.NoError(t, f.SetSheetVisible("Sheet2", false)) assert.NoError(t, f.SetSheetVisible("Sheet2", false, true)) assert.NoError(t, f.SetSheetVisible("Sheet1", false)) assert.NoError(t, f.SetSheetVisible("Sheet1", true)) visible, err := f.GetSheetVisible("Sheet1") assert.Equal(t, true, visible) assert.NoError(t, err) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSheetVisibility.xlsx"))) } func TestCopySheet(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) idx, err := f.NewSheet("CopySheet") assert.NoError(t, err) assert.NoError(t, f.CopySheet(0, idx)) assert.NoError(t, f.SetCellValue("CopySheet", "F1", "Hello")) val, err := f.GetCellValue("Sheet1", "F1") assert.NoError(t, err) assert.NotEqual(t, "Hello", val) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCopySheet.xlsx"))) } func TestCopySheetError(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) assert.EqualError(t, f.copySheet(-1, -2), ErrSheetNameBlank.Error()) assert.EqualError(t, f.CopySheet(-1, -2), ErrSheetIdx.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCopySheetError.xlsx"))) } func TestGetSheetComments(t *testing.T) { f := NewFile() assert.Empty(t, f.getSheetComments("sheet0")) } func TestGetActiveSheetIndex(t *testing.T) { f := NewFile() f.WorkBook.BookViews = nil assert.Equal(t, 0, f.GetActiveSheetIndex()) } func TestRelsWriter(t *testing.T) { f := NewFile() f.Relationships.Store("xl/worksheets/sheet/rels/sheet1.xml.rel", &xlsxRelationships{}) f.relsWriter() } func TestConditionalFormat(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) assert.NoError(t, fillCells(f, sheet1, 10, 15)) var format1, format2, format3, format4 int var err error // Rose format for bad conditional format1, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) // Light yellow format for neutral conditional format2, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEEAA0"}, Pattern: 1}}) assert.NoError(t, err) // Light green format for good conditional format3, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "09600B"}, Fill: Fill{Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1}}) assert.NoError(t, err) // conditional style with align and left border format4, err = f.NewConditionalStyle(&Style{Alignment: &Alignment{WrapText: true}, Border: []Border{{Type: "left", Color: "000000", Style: 1}}}) assert.NoError(t, err) // Color scales: 2 color assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10", []ConditionalFormatOptions{ { Type: "2_color_scale", Criteria: "=", MinType: "min", MaxType: "max", MinColor: "#F8696B", MaxColor: "#63BE7B", }, }, )) // Color scales: 3 color assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10", []ConditionalFormatOptions{ { Type: "3_color_scale", Criteria: "=", MinType: "min", MidType: "percentile", MaxType: "max", MinColor: "#F8696B", MidColor: "#FFEB84", MaxColor: "#63BE7B", }, }, )) // Highlight cells rules: between... assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10", []ConditionalFormatOptions{ { Type: "cell", Criteria: "between", Format: &format1, MinValue: "6", MaxValue: "8", }, }, )) // Highlight cells rules: Greater Than... assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10", []ConditionalFormatOptions{ { Type: "cell", Criteria: ">", Format: &format3, Value: "6", }, }, )) // Highlight cells rules: Equal To... assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10", []ConditionalFormatOptions{ { Type: "top", Criteria: "=", Format: &format3, }, }, )) // Highlight cells rules: Not Equal To... assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10", []ConditionalFormatOptions{ { Type: "unique", Criteria: "=", Format: &format2, }, }, )) // Highlight cells rules: Duplicate Values... assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10", []ConditionalFormatOptions{ { Type: "duplicate", Criteria: "=", Format: &format2, }, }, )) // Top/Bottom rules: Top 10%. assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10", []ConditionalFormatOptions{ { Type: "top", Criteria: "=", Format: &format1, Value: "6", Percent: true, }, }, )) // Top/Bottom rules: Above Average... assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10", []ConditionalFormatOptions{ { Type: "average", Criteria: "=", Format: &format3, AboveAverage: true, }, }, )) // Top/Bottom rules: Below Average... assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10", []ConditionalFormatOptions{ { Type: "average", Criteria: "=", Format: &format1, AboveAverage: false, }, }, )) // Data Bars: Gradient Fill assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", []ConditionalFormatOptions{ { Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", }, }, )) // Use a formula to determine which cells to format assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10", []ConditionalFormatOptions{ { Type: "formula", Criteria: "L2<3", Format: &format1, }, }, )) // Alignment/Border cells rules assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10", []ConditionalFormatOptions{ { Type: "cell", Criteria: ">", Format: &format4, Value: "0", }, }, )) // Test set conditional format with invalid cell reference assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.SetConditionalFormat("Sheet1", "A1:-", nil)) // Test set conditional format on not exists worksheet assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist") // Test set conditional format with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetConditionalFormat("Sheet:1", "L1:L10", nil)) err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx")) assert.NoError(t, err) // Set conditional format with illegal valid type assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat(sheet1, "K1:K10", []ConditionalFormatOptions{ { Type: "", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", }, }, )) // Set conditional format with illegal criteria type assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat(sheet1, "K1:K10", []ConditionalFormatOptions{ { Type: "data_bar", Criteria: "", MinType: "min", MaxType: "max", BarColor: "#638EC6", }, }, )) // Test create conditional format with invalid custom number format var exp string _, err = f.NewConditionalStyle(&Style{CustomNumFmt: &exp}) assert.Equal(t, ErrCustomNumFmt, err) // Set conditional format with file without dxfs element should not return error f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) _, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) assert.NoError(t, f.Close()) } func TestSharedStrings(t *testing.T) { f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx")) assert.NoError(t, err) rows, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.Equal(t, "A", rows[0][0]) rows, err = f.GetRows("Sheet2") assert.NoError(t, err) assert.Equal(t, "Test Weight (Kgs)", rows[0][0]) assert.NoError(t, f.Close()) } func TestSetSheetCol(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetSheetCol("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) assert.EqualError(t, f.SetSheetCol("Sheet1", "", &[]interface{}{"cell", nil, 2}), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) // Test set worksheet column values with invalid sheet name assert.EqualError(t, f.SetSheetCol("Sheet:1", "A1", &[]interface{}{nil}), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.SetSheetCol("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error()) assert.EqualError(t, f.SetSheetCol("Sheet1", "B27", &f), ErrParameterInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetCol.xlsx"))) assert.NoError(t, f.Close()) } func TestSetSheetRow(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) assert.EqualError(t, f.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) // Test set worksheet row with invalid sheet name assert.EqualError(t, f.SetSheetRow("Sheet:1", "A1", &[]interface{}{1}), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error()) assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", &f), ErrParameterInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx"))) assert.NoError(t, f.Close()) } func TestHSL(t *testing.T) { var hsl HSL r, g, b, a := hsl.RGBA() assert.Equal(t, uint32(0), r) assert.Equal(t, uint32(0), g) assert.Equal(t, uint32(0), b) assert.Equal(t, uint32(0xffff), a) assert.Equal(t, HSL{0, 0, 0}, hslModel(hsl)) assert.Equal(t, HSL{0, 0, 0}, hslModel(color.Gray16{Y: uint16(1)})) R, G, B := HSLToRGB(0, 1, 0.4) assert.Equal(t, uint8(204), R) assert.Equal(t, uint8(0), G) assert.Equal(t, uint8(0), B) R, G, B = HSLToRGB(0, 1, 0.6) assert.Equal(t, uint8(255), R) assert.Equal(t, uint8(51), G) assert.Equal(t, uint8(51), B) assert.Equal(t, 0.0, hueToRGB(0, 0, -1)) assert.Equal(t, 0.0, hueToRGB(0, 0, 2)) assert.Equal(t, 0.0, hueToRGB(0, 0, 1.0/7)) assert.Equal(t, 0.0, hueToRGB(0, 0, 0.4)) assert.Equal(t, 0.0, hueToRGB(0, 0, 2.0/4)) h, s, l := RGBToHSL(255, 255, 0) assert.Equal(t, 0.16666666666666666, h) assert.Equal(t, 1.0, s) assert.Equal(t, 0.5, l) h, s, l = RGBToHSL(0, 255, 255) assert.Equal(t, 0.5, h) assert.Equal(t, 1.0, s) assert.Equal(t, 0.5, l) h, s, l = RGBToHSL(250, 100, 50) assert.Equal(t, 0.041666666666666664, h) assert.Equal(t, 0.9523809523809524, s) assert.Equal(t, 0.5882352941176471, l) h, s, l = RGBToHSL(50, 100, 250) assert.Equal(t, 0.625, h) assert.Equal(t, 0.9523809523809524, s) assert.Equal(t, 0.5882352941176471, l) h, s, l = RGBToHSL(250, 50, 100) assert.Equal(t, 0.9583333333333334, h) assert.Equal(t, 0.9523809523809524, s) assert.Equal(t, 0.5882352941176471, l) } func TestProtectSheet(t *testing.T) { f := NewFile() sheetName := f.GetSheetName(0) assert.EqualError(t, f.ProtectSheet(sheetName, nil), ErrParameterInvalid.Error()) // Test protect worksheet with XOR hash algorithm assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{ Password: "password", EditScenarios: false, })) opts, err := f.GetSheetProtection(sheetName) assert.NoError(t, err) assert.Equal(t, false, opts.EditScenarios) ws, err := f.workSheetReader(sheetName) assert.NoError(t, err) assert.Equal(t, "83AF", ws.SheetProtection.Password) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectSheet.xlsx"))) // Test protect worksheet with SHA-512 hash algorithm assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{ AlgorithmName: "SHA-512", Password: "password", })) ws, err = f.workSheetReader(sheetName) assert.NoError(t, err) assert.Len(t, ws.SheetProtection.SaltValue, 24) assert.Len(t, ws.SheetProtection.HashValue, 88) assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount) // Test remove sheet protection with an incorrect password assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error()) // Test remove sheet protection with invalid sheet name assert.EqualError(t, f.UnprotectSheet("Sheet:1", "wrongPassword"), ErrSheetNameInvalid.Error()) // Test remove sheet protection with password verification assert.NoError(t, f.UnprotectSheet(sheetName, "password")) // Test protect worksheet with empty password assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{})) assert.Empty(t, ws.SheetProtection.Password) // Test protect worksheet with password exceeds the limit length assert.EqualError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{ AlgorithmName: "MD4", Password: strings.Repeat("s", MaxFieldLength+1), }), ErrPasswordLengthInvalid.Error()) // Test protect worksheet with unsupported hash algorithm assert.EqualError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{ AlgorithmName: "RIPEMD-160", Password: "password", }), ErrUnsupportedHashAlgorithm.Error()) // Test protect not exists worksheet assert.EqualError(t, f.ProtectSheet("SheetN", nil), "sheet SheetN does not exist") // Test protect sheet with invalid sheet name assert.EqualError(t, f.ProtectSheet("Sheet:1", nil), ErrSheetNameInvalid.Error()) // Test get sheet protection on not exists worksheet _, err = f.GetSheetProtection("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") } func TestUnprotectSheet(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test remove protection on not exists worksheet assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist") assert.NoError(t, f.UnprotectSheet("Sheet1")) assert.EqualError(t, f.UnprotectSheet("Sheet1", "password"), ErrUnprotectSheet.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectSheet.xlsx"))) assert.NoError(t, f.Close()) f = NewFile() sheetName := f.GetSheetName(0) assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{Password: "password"})) // Test remove sheet protection with an incorrect password assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error()) // Test remove sheet protection with password verification assert.NoError(t, f.UnprotectSheet(sheetName, "password")) // Test with invalid salt value assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{ AlgorithmName: "SHA-512", Password: "password", })) ws, err := f.workSheetReader(sheetName) assert.NoError(t, err) ws.SheetProtection.SaltValue = "YWJjZA=====" assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), "illegal base64 data at input byte 8") } func TestProtectWorkbook(t *testing.T) { f := NewFile() assert.NoError(t, f.ProtectWorkbook(nil)) // Test protect workbook with default hash algorithm assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ Password: "password", LockStructure: true, })) wb, err := f.workbookReader() assert.NoError(t, err) assert.Equal(t, "SHA-512", wb.WorkbookProtection.WorkbookAlgorithmName) assert.Len(t, wb.WorkbookProtection.WorkbookSaltValue, 24) assert.Len(t, wb.WorkbookProtection.WorkbookHashValue, 88) assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount) // Test protect workbook with password exceeds the limit length assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ AlgorithmName: "MD4", Password: strings.Repeat("s", MaxFieldLength+1), }), ErrPasswordLengthInvalid.Error()) // Test protect workbook with unsupported hash algorithm assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ AlgorithmName: "RIPEMD-160", Password: "password", }), ErrUnsupportedHashAlgorithm.Error()) // Test protect workbook with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.ProtectWorkbook(nil), "XML syntax error on line 1: invalid UTF-8") } func TestUnprotectWorkbook(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.UnprotectWorkbook()) assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectWorkbook.xlsx"))) assert.NoError(t, f.Close()) f = NewFile() assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{Password: "password"})) // Test remove workbook protection with an incorrect password assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), ErrUnprotectWorkbookPassword.Error()) // Test remove workbook protection with password verification assert.NoError(t, f.UnprotectWorkbook("password")) // Test with invalid salt value assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ AlgorithmName: "SHA-512", Password: "password", })) wb, err := f.workbookReader() assert.NoError(t, err) wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA=====" assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8") // Test remove workbook protection with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.UnprotectWorkbook(), "XML syntax error on line 1: invalid UTF-8") } func TestSetDefaultTimeStyle(t *testing.T) { f := NewFile() // Test set default time style on not exists worksheet. assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN does not exist") // Test set default time style on invalid cell assert.EqualError(t, f.setDefaultTimeStyle("Sheet1", "", 42), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) } func TestAddVBAProject(t *testing.T) { f := NewFile() file, err := os.ReadFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")})) assert.EqualError(t, f.AddVBAProject(file), ErrAddVBAProject.Error()) file, err = os.ReadFile(filepath.Join("test", "vbaProject.bin")) assert.NoError(t, err) assert.NoError(t, f.AddVBAProject(file)) // Test add VBA project twice assert.NoError(t, f.AddVBAProject(file)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) // Test add VBA with unsupported charset workbook relationships f.Relationships.Delete(defaultXMLPathWorkbookRels) f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset) assert.EqualError(t, f.AddVBAProject(file), "XML syntax error on line 1: invalid UTF-8") } func TestContentTypesReader(t *testing.T) { // Test unsupported charset f := NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) _, err := f.contentTypesReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestWorkbookReader(t *testing.T) { // Test unsupported charset f := NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err := f.workbookReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestWorkSheetReader(t *testing.T) { // Test unsupported charset f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err := f.workSheetReader("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") // Test on no checked worksheet f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) f.checked = sync.Map{} _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) } func TestRelsReader(t *testing.T) { // Test unsupported charset f := NewFile() rels := defaultXMLPathWorkbookRels f.Relationships.Store(rels, nil) f.Pkg.Store(rels, MacintoshCyrillicCharset) _, err := f.relsReader(rels) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestDeleteSheetFromWorkbookRels(t *testing.T) { f := NewFile() rels := defaultXMLPathWorkbookRels f.Relationships.Store(rels, nil) assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "") } func TestUpdateLinkedValue(t *testing.T) { f := NewFile() // Test update lined value with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") } func TestAttrValToInt(t *testing.T) { _, err := attrValToInt("r", []xml.Attr{ {Name: xml.Name{Local: "r"}, Value: "s"}, }) assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`) } func prepareTestBook1() (*File, error) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if err != nil { return nil, err } if err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}); err != nil { return nil, err } // Test add picture to worksheet with offset, external hyperlink and positioning if err := f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"), &GraphicOptions{ OffsetX: 10, OffsetY: 10, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell", }, ); err != nil { return nil, err } file, err := os.ReadFile(filepath.Join("test", "images", "excel.jpg")) if err != nil { return nil, err } err = f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".jpg", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}) if err != nil { return nil, err } return f, nil } func prepareTestBook3() (*File, error) { f := NewFile() if _, err := f.NewSheet("Sheet2"); err != nil { return nil, err } if _, err := f.NewSheet("Sheet3"); err != nil { return nil, err } if err := f.SetCellInt("Sheet2", "A23", 56); err != nil { return nil, err } if err := f.SetCellStr("Sheet1", "B20", "42"); err != nil { return nil, err } f.SetActiveSheet(0) if err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), &GraphicOptions{ScaleX: 0.5, ScaleY: 0.5, Positioning: "absolute"}); err != nil { return nil, err } if err := f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil); err != nil { return nil, err } return f, nil } func prepareTestBook4() (*File, error) { f := NewFile() if err := f.SetColWidth("Sheet1", "B", "A", 12); err != nil { return f, err } if err := f.SetColWidth("Sheet1", "A", "B", 12); err != nil { return f, err } if _, err := f.GetColWidth("Sheet1", "A"); err != nil { return f, err } if _, err := f.GetColWidth("Sheet1", "C"); err != nil { return f, err } return f, nil } func prepareTestBook5(opts Options) (*File, error) { f := NewFile(opts) var rowNum int for _, idxRange := range [][]int{{27, 36}, {50, 81}} { for numFmtIdx := idxRange[0]; numFmtIdx <= idxRange[1]; numFmtIdx++ { rowNum++ styleID, err := f.NewStyle(&Style{NumFmt: numFmtIdx}) if err != nil { return f, err } cell, err := CoordinatesToCellName(1, rowNum) if err != nil { return f, err } if err := f.SetCellValue("Sheet1", cell, 45162); err != nil { return f, err } if err := f.SetCellStyle("Sheet1", cell, cell, styleID); err != nil { return f, err } } } return f, nil } func fillCells(f *File, sheet string, colCount, rowCount int) error { for col := 1; col <= colCount; col++ { for row := 1; row <= rowCount; row++ { cell, _ := CoordinatesToCellName(col, row) if err := f.SetCellStr(sheet, cell, cell); err != nil { return err } } } return nil } func BenchmarkOpenFile(b *testing.B) { for i := 0; i < b.N; i++ { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if err != nil { b.Error(err) } if err := f.Close(); err != nil { b.Error(err) } } } ================================================ FILE: file.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/binary" "encoding/xml" "io" "math" "os" "path/filepath" "sort" "strings" "sync" ) // NewFile provides a function to create new file by default template. // For example: // // f := NewFile() func NewFile(opts ...Options) *File { f := newFile() f.Pkg.Store(defaultXMLPathRels, []byte(xml.Header+templateRels)) f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp)) f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore)) f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels)) f.Pkg.Store(defaultXMLPathTheme, []byte(xml.Header+templateTheme)) f.Pkg.Store(defaultXMLPathSheet, []byte(xml.Header+templateSheet)) f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles)) f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook)) f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes)) f.SheetCount = 1 f.CalcChain, _ = f.calcChainReader() f.ContentTypes, _ = f.contentTypesReader() f.Styles, _ = f.stylesReader() f.WorkBook, _ = f.workbookReader() f.Relationships = sync.Map{} rels, _ := f.relsReader(defaultXMLPathWorkbookRels) f.Relationships.Store(defaultXMLPathWorkbookRels, rels) f.sheetMap["Sheet1"] = defaultXMLPathSheet ws, _ := f.workSheetReader("Sheet1") f.Sheet.Store(defaultXMLPathSheet, ws) f.Theme, _ = f.themeReader() f.options = f.getOptions(opts...) return f } // Save provides a function to override the spreadsheet with origin path. func (f *File) Save(opts ...Options) error { if f.Path == "" { return ErrSave } for i := range opts { f.options = &opts[i] } return f.SaveAs(f.Path, *f.options) } // SaveAs provides a function to create or update to a spreadsheet at the // provided path. func (f *File) SaveAs(name string, opts ...Options) error { if countUTF16String(name) > MaxFilePathLength { return ErrMaxFilePathLength } f.Path = name if _, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]; !ok { return ErrWorkbookFileFormat } file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) if err != nil { return err } defer func() { _ = file.Close() }() return f.Write(file, opts...) } // Close closes and cleanup the open temporary file for the spreadsheet. func (f *File) Close() error { var firstErr error if f.sharedStringTemp != nil { firstErr = f.sharedStringTemp.Close() f.sharedStringTemp = nil } for _, stream := range f.streams { _ = stream.rawData.Close() } f.streams = nil f.tempFiles.Range(func(k, v interface{}) bool { if err := os.Remove(v.(string)); err != nil && firstErr == nil { firstErr = err } return true }) f.tempFiles.Clear() return firstErr } // Write provides a function to write to an io.Writer. func (f *File) Write(w io.Writer, opts ...Options) error { _, err := f.WriteTo(w, opts...) return err } // WriteTo implements io.WriterTo to write the file. func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) { for i := range opts { f.options = &opts[i] } if len(f.Path) != 0 { contentType, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))] if !ok { return 0, ErrWorkbookFileFormat } if err := f.setContentTypePartProjectExtensions(contentType); err != nil { return 0, err } } buf, err := f.WriteToBuffer() if err != nil { return 0, err } return buf.WriteTo(w) } // WriteToBuffer provides a function to get bytes.Buffer from the saved file, // and it allocates space in memory. Be careful when the file size is large. func (f *File) WriteToBuffer() (*bytes.Buffer, error) { buf := new(bytes.Buffer) zw := f.ZipWriter(buf) if err := f.writeToZip(zw); err != nil { _ = zw.Close() return buf, err } if err := zw.Close(); err != nil { return buf, err } err := f.writeZip64LFH(buf) if f.options != nil && f.options.Password != "" { b, err := Encrypt(buf.Bytes(), f.options) if err != nil { return buf, err } buf.Reset() buf.Write(b) } return buf, err } // writeToZip provides a function to write to ZipWriter. func (f *File) writeToZip(zw ZipWriter) error { f.calcChainWriter() f.commentsWriter() f.contentTypesWriter() f.drawingsWriter() f.volatileDepsWriter() f.vmlDrawingWriter() f.workBookWriter() f.workSheetWriter() f.relsWriter() _ = f.sharedStringsLoader() f.sharedStringsWriter() f.styleSheetWriter() f.themeWriter() for path, stream := range f.streams { fi, err := zw.Create(path) if err != nil { return err } var from io.Reader if from, err = stream.rawData.Reader(); err != nil { _ = stream.rawData.Close() return err } written, err := io.Copy(fi, from) if err != nil { return err } if written > math.MaxUint32 { f.zip64Entries = append(f.zip64Entries, path) } } var ( n int err error files, tempFiles []string ) f.Pkg.Range(func(path, content interface{}) bool { if _, ok := f.streams[path.(string)]; ok { return true } files = append(files, path.(string)) return true }) sort.Sort(sort.Reverse(sort.StringSlice(files))) for _, path := range files { var fi io.Writer if fi, err = zw.Create(path); err != nil { break } content, _ := f.Pkg.Load(path) if n, err = fi.Write(content.([]byte)); int64(n) > math.MaxUint32 { f.zip64Entries = append(f.zip64Entries, path) } } f.tempFiles.Range(func(path, content interface{}) bool { if _, ok := f.Pkg.Load(path); ok { return true } tempFiles = append(tempFiles, path.(string)) return true }) sort.Sort(sort.Reverse(sort.StringSlice(tempFiles))) for _, path := range tempFiles { var fi io.Writer if fi, err = zw.Create(path); err != nil { break } if n, err = fi.Write(f.readBytes(path)); int64(n) > math.MaxUint32 { f.zip64Entries = append(f.zip64Entries, path) } } return err } // writeZip64LFH function sets the ZIP version to 0x2D (45) in the Local File // Header (LFH). Excel strictly enforces ZIP64 format validation rules. When any // file within the workbook (OCP) exceeds 4GB in size, the ZIP64 format must be // used according to the PKZIP specification. However, ZIP files generated using // Go's standard archive/zip library always set the version in the local file // header to 20 (ZIP version 2.0) by default, as defined in the internal // 'writeHeader' function during ZIP creation. The archive/zip package only sets // the 'ReaderVersion' to 45 (ZIP64 version 4.5) in the central directory for // entries larger than 4GB. This results in a version mismatch between the // central directory and the local file header. As a result, opening the // generated workbook with spreadsheet application will prompt file corruption. func (f *File) writeZip64LFH(buf *bytes.Buffer) error { if len(f.zip64Entries) == 0 { return nil } data, offset := buf.Bytes(), 0 for offset < len(data) { idx := bytes.Index(data[offset:], []byte{0x50, 0x4b, 0x03, 0x04}) if idx == -1 { break } idx += offset if idx+30 > len(data) { break } filenameLen := int(binary.LittleEndian.Uint16(data[idx+26 : idx+28])) if idx+30+filenameLen > len(data) { break } if inStrSlice(f.zip64Entries, string(data[idx+30:idx+30+filenameLen]), true) != -1 { binary.LittleEndian.PutUint16(data[idx+4:idx+6], 45) } offset = idx + 1 } return nil } ================================================ FILE: file_test.go ================================================ package excelize import ( "archive/zip" "bufio" "bytes" "encoding/binary" "errors" "io" "io/fs" "math" "os" "path/filepath" "strconv" "strings" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // errZipWriter is a mock ZipWriter whose Create and Close can be configured to // return errors. type errZipWriter struct { createFunc func(string) (io.Writer, error) closeErr error } func (m *errZipWriter) Create(name string) (io.Writer, error) { if m.createFunc != nil { return m.createFunc(name) } return &bytes.Buffer{}, nil } func (m *errZipWriter) AddFS(fs.FS) error { return nil } func (m *errZipWriter) Close() error { return m.closeErr } type errWriter struct{ err error } func (e *errWriter) Write([]byte) (int, error) { return 0, e.err } func BenchmarkWrite(b *testing.B) { const s = "This is test data" for b.Loop() { f := NewFile() for row := 1; row <= 10000; row++ { for col := 1; col <= 20; col++ { val, err := CoordinatesToCellName(col, row) if err != nil { b.Error(err) } if err := f.SetCellValue("Sheet1", val, s); err != nil { b.Error(err) } } } // Save spreadsheet by the given path. if err := f.SaveAs("test.xlsx"); err != nil { b.Error(err) } } } func TestWriteTo(t *testing.T) { // Test WriteToBuffer err { f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{} f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) }) f.Pkg.Store("/d/", []byte("s")) _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, "zip: write to directory") f.Pkg.Delete("/d/") } // Test file path overflow { f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{} f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) }) const maxUint16 = 1<<16 - 1 f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil) _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, "zip: FileHeader.Name too long") } // Test StreamsWriter err { f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{} f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) }) f.Pkg.Store("s", nil) f.streams = make(map[string]*StreamWriter) file, _ := os.Open("123") f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}} _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.Nil(t, err) } // Test write with temporary file { f, buf := File{tempFiles: sync.Map{}}, bytes.Buffer{} f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) }) const maxUint16 = 1<<16 - 1 f.tempFiles.Store("s", "") f.tempFiles.Store(strings.Repeat("s", maxUint16+1), "") _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, "zip: FileHeader.Name too long") } // Test write with unsupported workbook file format { f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{} f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) }) f.Pkg.Store("/d", []byte("s")) f.Path = "Book1.xls" _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) } // Test write with unsupported charset content types. { f, buf := NewFile(), bytes.Buffer{} f.ContentTypes, f.Path = nil, filepath.Join("test", "TestWriteTo.xlsx") f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } // Test WriteToBuffer with ZipWriter Close error { f := NewFile() f.SetZipWriter(func(w io.Writer) ZipWriter { return &errZipWriter{closeErr: errors.New("close error")} }) _, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{})) assert.EqualError(t, err, "close error") } // Test writeToZip with stream Create error { f := NewFile() f.streams = make(map[string]*StreamWriter) f.streams["s"] = &StreamWriter{rawData: bufferedWriter{}} f.SetZipWriter(func(w io.Writer) ZipWriter { return &errZipWriter{ createFunc: func(name string) (io.Writer, error) { if name == "s" { return nil, errors.New("create stream error") } return &bytes.Buffer{}, nil }, } }) _, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{})) assert.EqualError(t, err, "create stream error") } // Test writeToZip with stream rawData.Reader() error { f := NewFile() f.streams = make(map[string]*StreamWriter) tmp, err := os.CreateTemp("", "excelize-test-*") assert.NoError(t, err) assert.NoError(t, tmp.Close()) f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: tmp}} _, err = f.WriteTo(bufio.NewWriter(&bytes.Buffer{})) assert.Error(t, err) } // Test writeToZip with io.Copy error on stream { f := NewFile() f.streams = make(map[string]*StreamWriter) sw := &StreamWriter{} sw.rawData.buf.WriteString("test data") f.streams["s"] = sw f.SetZipWriter(func(w io.Writer) ZipWriter { return &errZipWriter{ createFunc: func(name string) (io.Writer, error) { if name == "s" { return &errWriter{err: errors.New("copy error")}, nil } return &bytes.Buffer{}, nil }, } }) _, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{})) assert.EqualError(t, err, "copy error") } } func TestClose(t *testing.T) { f := NewFile() f.tempFiles.Store("/d/", "/d/") require.Error(t, f.Close()) } func TestZip64(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) sw, err := f.NewStreamWriter("Sheet1") assert.NoError(t, err) for r := range 131 { rowData := make([]interface{}, 1000) for c := range 1000 { rowData[c] = strings.Repeat("c", TotalCellChars) } cell, err := CoordinatesToCellName(1, r+1) assert.NoError(t, err) assert.NoError(t, sw.SetRow(cell, rowData)) } assert.NoError(t, sw.Flush()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestZip64.xlsx"))) assert.NoError(t, f.Close()) // Test with filename length overflow f = NewFile() f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings) buf := new(bytes.Buffer) buf.Write([]byte{0x50, 0x4b, 0x03, 0x04}) buf.Write(make([]byte, 20)) assert.NoError(t, f.writeZip64LFH(buf)) // Test with file header less than the required 30 for the fixed header part f = NewFile() f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings) buf.Reset() buf.Write([]byte{0x50, 0x4b, 0x03, 0x04}) buf.Write(make([]byte, 22)) assert.NoError(t, binary.Write(buf, binary.LittleEndian, uint16(10))) buf.Write(make([]byte, 2)) buf.WriteString("test") assert.NoError(t, f.writeZip64LFH(buf)) t.Run("for_save_zip64_with_in_memory_file_over_4GB", func(t *testing.T) { // Test save workbook in ZIP64 format with in memory file with size over 4GB. f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", make([]byte, math.MaxUint32+1)) _, err := f.WriteToBuffer() assert.NoError(t, err) assert.NoError(t, f.Close()) }) t.Run("for_save_zip64_with_in_temporary_file_over_4GB", func(t *testing.T) { // Test save workbook in ZIP64 format with temporary file with size over 4GB. if os.Getenv("GITHUB_ACTIONS") == "true" { t.Skip() } f := NewFile() f.Pkg.Delete("xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml") tmp, err := os.CreateTemp(os.TempDir(), "excelize-") assert.NoError(t, err) assert.NoError(t, tmp.Truncate(math.MaxUint32+1)) f.tempFiles.Store("xl/worksheets/sheet1.xml", tmp.Name()) assert.NoError(t, tmp.Close()) _, err = f.WriteToBuffer() assert.NoError(t, err) assert.NoError(t, f.Close()) }) } func TestRemoveTempFiles(t *testing.T) { tmp, err := os.CreateTemp("", "excelize-*") if err != nil { t.Fatal(err) } tmpName := tmp.Name() assert.NoError(t, tmp.Close()) f := NewFile() // Fill the tempFiles map with non-existing files for i := range 1000 { f.tempFiles.Store(strconv.Itoa(i), "/hopefully not existing") } f.tempFiles.Store("existing", tmpName) require.Error(t, f.Close()) if _, err := os.Stat(tmpName); err == nil { t.Errorf("temp file %q still exist", tmpName) assert.NoError(t, os.Remove(tmpName)) } } ================================================ FILE: go.mod ================================================ module github.com/xuri/excelize/v2 go 1.25.0 require ( github.com/richardlehane/mscfb v1.0.6 github.com/stretchr/testify v1.11.1 github.com/tiendc/go-deepcopy v1.7.2 github.com/xuri/efp v0.0.1 github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 golang.org/x/crypto v0.49.0 golang.org/x/image v0.25.0 golang.org/x/net v0.52.0 golang.org/x/text v0.35.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/msoleps v1.0.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8= github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: hsl.go ================================================ // Copyright (c) 2012 Rodrigo Moraes. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package excelize import ( "image/color" "math" ) // HSLModel converts any color.Color to a HSL color. var HSLModel = color.ModelFunc(hslModel) // HSL represents a cylindrical coordinate of points in an RGB color model. // // Values are in the range 0 to 1. type HSL struct { H, S, L float64 } // RGBA returns the alpha-premultiplied red, green, blue and alpha values // for the HSL. func (c HSL) RGBA() (uint32, uint32, uint32, uint32) { r, g, b := HSLToRGB(c.H, c.S, c.L) return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff } // hslModel converts a color.Color to HSL. func hslModel(c color.Color) color.Color { if _, ok := c.(HSL); ok { return c } r, g, b, _ := c.RGBA() h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) return HSL{h, s, l} } // RGBToHSL converts an RGB triple to an HSL triple. func RGBToHSL(r, g, b uint8) (h, s, l float64) { fR := float64(r) / 255 fG := float64(g) / 255 fB := float64(b) / 255 maxVal := math.Max(math.Max(fR, fG), fB) minVal := math.Min(math.Min(fR, fG), fB) l = (maxVal + minVal) / 2 if maxVal == minVal { // Achromatic. h, s = 0, 0 } else { // Chromatic. d := maxVal - minVal if l > 0.5 { s = d / (2.0 - maxVal - minVal) } else { s = d / (maxVal + minVal) } switch maxVal { case fR: h = (fG - fB) / d if fG < fB { h += 6 } case fG: h = (fB-fR)/d + 2 case fB: h = (fR-fG)/d + 4 } h /= 6 } return } // HSLToRGB converts an HSL triple to an RGB triple. func HSLToRGB(h, s, l float64) (r, g, b uint8) { var fR, fG, fB float64 if s == 0 { fR, fG, fB = l, l, l } else { var q float64 if l < 0.5 { q = l * (1 + s) } else { q = l + s - s*l } p := 2*l - q fR = hueToRGB(p, q, h+1.0/3) fG = hueToRGB(p, q, h) fB = hueToRGB(p, q, h-1.0/3) } r = uint8((fR * 255) + 0.5) g = uint8((fG * 255) + 0.5) b = uint8((fB * 255) + 0.5) return } // hueToRGB is a helper function for HSLToRGB. func hueToRGB(p, q, t float64) float64 { if t < 0 { t++ } if t > 1 { t-- } if t < 1.0/6 { return p + (q-p)*6*t } if t < 0.5 { return q } if t < 2.0/3 { return p + (q-p)*(2.0/3-t)*6 } return p } ================================================ FILE: lib.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "archive/zip" "bytes" "container/list" "encoding/xml" "fmt" "io" "math" "math/big" "os" "reflect" "regexp" "sort" "strconv" "strings" "unicode/utf16" ) // ReadZipReader extract spreadsheet with given options. func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) { var ( err error docPart = map[string]string{ "[content_types].xml": defaultXMLPathContentTypes, "xl/sharedstrings.xml": defaultXMLPathSharedStrings, } fileList = make(map[string][]byte, len(r.File)) worksheets int unzipSize int64 ) for _, v := range r.File { fileSize := v.FileInfo().Size() unzipSize += fileSize if unzipSize > f.options.UnzipSizeLimit { return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit) } fileName := strings.ReplaceAll(v.Name, "\\", "/") if partName, ok := docPart[strings.ToLower(fileName)]; ok { fileName = partName } if strings.EqualFold(fileName, defaultXMLPathSharedStrings) && fileSize > f.options.UnzipXMLSizeLimit { tempFile, err := f.unzipToTemp(v) if tempFile != "" { f.tempFiles.Store(fileName, tempFile) } if err == nil { continue } } if strings.HasPrefix(strings.ToLower(fileName), "xl/worksheets/sheet") { worksheets++ if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() { tempFile, err := f.unzipToTemp(v) if tempFile != "" { f.tempFiles.Store(fileName, tempFile) } if err == nil { continue } } } if fileList[fileName], err = readFile(v); err != nil { return nil, 0, err } } return fileList, worksheets, nil } // unzipToTemp unzip the zip entity to the system temporary directory and // returned the unzipped file path. func (f *File) unzipToTemp(zipFile *zip.File) (string, error) { tmp, err := os.CreateTemp(f.options.TmpDir, "excelize-") if err != nil { return "", err } rc, err := zipFile.Open() if err != nil { return tmp.Name(), err } if _, err = io.Copy(tmp, rc); err != nil { return tmp.Name(), err } if err = rc.Close(); err != nil { return tmp.Name(), err } return tmp.Name(), tmp.Close() } // readXML provides a function to read XML content as bytes. func (f *File) readXML(name string) []byte { if content, _ := f.Pkg.Load(name); content != nil { return content.([]byte) } if content, ok := f.streams[name]; ok { return content.rawData.buf.Bytes() } return []byte{} } // readBytes read file as bytes by given path. func (f *File) readBytes(name string) []byte { content := f.readXML(name) if len(content) != 0 { return content } file, err := f.readTemp(name) if err != nil { return content } content, _ = io.ReadAll(file) f.Pkg.Store(name, content) _ = file.Close() return content } // readTemp read file from system temporary directory by given path. func (f *File) readTemp(name string) (file *os.File, err error) { path, ok := f.tempFiles.Load(name) if !ok { return } file, err = os.Open(path.(string)) return } // saveFileList provides a function to update given file content in file list // of spreadsheet. func (f *File) saveFileList(name string, content []byte) { f.Pkg.Store(name, append([]byte(xml.Header), content...)) } // Read file content as string in an archive file. func readFile(file *zip.File) ([]byte, error) { rc, err := file.Open() if err != nil { return nil, err } dat := make([]byte, 0, file.FileInfo().Size()) buff := bytes.NewBuffer(dat) _, _ = io.Copy(buff, rc) return buff.Bytes(), rc.Close() } // SplitCellName splits cell name to column name and row number. // // Example: // // excelize.SplitCellName("AK74") // return "AK", 74, nil func SplitCellName(cell string) (string, int, error) { alpha := func(r rune) bool { return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || (r == 36) } if strings.IndexFunc(cell, alpha) == 0 { i := strings.LastIndexFunc(cell, alpha) if i >= 0 && i < len(cell)-1 { col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:] if row, err := strconv.Atoi(rowStr); err == nil && row > 0 { return col, row, nil } } } return "", -1, newInvalidCellNameError(cell) } // JoinCellName joins cell name from column name and row number. func JoinCellName(col string, row int) (string, error) { normCol := strings.Map(func(rune rune) rune { switch { case 'A' <= rune && rune <= 'Z': return rune case 'a' <= rune && rune <= 'z': return rune - 32 } return -1 }, col) if len(col) == 0 || len(col) != len(normCol) { return "", newInvalidColumnNameError(col) } if row < 1 { return "", newInvalidRowNumberError(row) } return normCol + strconv.Itoa(row), nil } // ColumnNameToNumber provides a function to convert Excel sheet column name // (case-insensitive) to int. The function returns an error if column name // incorrect. // // Example: // // excelize.ColumnNameToNumber("AK") // returns 37, nil func ColumnNameToNumber(name string) (int, error) { if len(name) == 0 { return -1, newInvalidColumnNameError(name) } col := 0 multi := 1 for i := len(name) - 1; i >= 0; i-- { r := name[i] if r >= 'A' && r <= 'Z' { col += int(r-'A'+1) * multi } else if r >= 'a' && r <= 'z' { col += int(r-'a'+1) * multi } else { return -1, newInvalidColumnNameError(name) } multi *= 26 } if col > MaxColumns { return -1, ErrColumnNumber } return col, nil } // ColumnNumberToName provides a function to convert the integer to Excel // sheet column title. // // Example: // // excelize.ColumnNumberToName(37) // returns "AK", nil func ColumnNumberToName(num int) (string, error) { if num < MinColumns || num > MaxColumns { return "", ErrColumnNumber } estimatedLength := 0 for n := num; n > 0; n = (n - 1) / 26 { estimatedLength++ } result := make([]byte, estimatedLength) for num > 0 { estimatedLength-- result[estimatedLength] = byte((num-1)%26 + 'A') num = (num - 1) / 26 } return string(result), nil } // CellNameToCoordinates converts alphanumeric cell name to [X, Y] coordinates // or returns an error. // // Example: // // excelize.CellNameToCoordinates("A1") // returns 1, 1, nil // excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil func CellNameToCoordinates(cell string) (int, int, error) { colName, row, err := SplitCellName(cell) if err != nil { return -1, -1, newCellNameToCoordinatesError(cell, err) } if row > TotalRows { return -1, -1, ErrMaxRows } col, err := ColumnNameToNumber(colName) return col, row, err } // CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell // name or returns an error. // // Example: // // excelize.CoordinatesToCellName(1, 1) // returns "A1", nil // excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil func CoordinatesToCellName(col, row int, abs ...bool) (string, error) { if col < 1 || row < 1 { return "", newCoordinatesToCellNameError(col, row) } if row > TotalRows { return "", ErrMaxRows } sign := "" for _, a := range abs { if a { sign = "$" } } colName, err := ColumnNumberToName(col) return sign + colName + sign + strconv.Itoa(row), err } // rangeRefToCoordinates provides a function to convert range reference to a // pair of coordinates. func rangeRefToCoordinates(ref string) ([]int, error) { rng := strings.Split(strings.ReplaceAll(ref, "$", ""), ":") if len(rng) < 2 { return nil, ErrParameterInvalid } return cellRefsToCoordinates(rng[0], rng[1]) } // cellRefsToCoordinates provides a function to convert cell range to a // pair of coordinates. func cellRefsToCoordinates(firstCell, lastCell string) ([]int, error) { coordinates := make([]int, 4) var err error coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell) if err != nil { return coordinates, err } coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell) return coordinates, err } // sortCoordinates provides a function to correct the cell range, such // correct C1:B3 to B1:C3. func sortCoordinates(coordinates []int) error { if len(coordinates) != 4 { return ErrCoordinates } if coordinates[2] < coordinates[0] { coordinates[2], coordinates[0] = coordinates[0], coordinates[2] } if coordinates[3] < coordinates[1] { coordinates[3], coordinates[1] = coordinates[1], coordinates[3] } return nil } // coordinatesToRangeRef provides a function to convert a pair of coordinates // to range reference. func coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) { if len(coordinates) != 4 { return "", ErrCoordinates } firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...) if err != nil { return "", err } lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...) if err != nil { return "", err } return firstCell + ":" + lastCell, err } // getDefinedNameRefTo convert defined name to reference range. func (f *File) getDefinedNameRefTo(definedNameName, currentSheet string) (refTo string) { var workbookRefTo, worksheetRefTo string for _, definedName := range f.GetDefinedName() { if definedName.Name == definedNameName { // worksheet scope takes precedence over scope workbook when both definedNames exist if definedName.Scope == "Workbook" { workbookRefTo = definedName.RefersTo } if definedName.Scope == currentSheet { worksheetRefTo = definedName.RefersTo } } } refTo = workbookRefTo if worksheetRefTo != "" { refTo = worksheetRefTo } return } // flatSqref convert reference sequence to cell reference list. func flatSqref(sqref string) (cells map[int][][]int, err error) { var coordinates []int cells = make(map[int][][]int) for _, ref := range strings.Fields(sqref) { rng := strings.Split(ref, ":") switch len(rng) { case 1: var col, row int col, row, err = CellNameToCoordinates(rng[0]) if err != nil { return } cells[col] = append(cells[col], []int{col, row}) case 2: if coordinates, err = rangeRefToCoordinates(ref); err != nil { return } _ = sortCoordinates(coordinates) for c := coordinates[0]; c <= coordinates[2]; c++ { for r := coordinates[1]; r <= coordinates[3]; r++ { cells[c] = append(cells[c], []int{c, r}) } } } } // Sort each column's cells by row number for col := range cells { sort.Slice(cells[col], func(i, j int) bool { return cells[col][i][1] < cells[col][j][1] }) } return } // inCoordinates provides a method to check if a coordinate is present in // coordinates array, and return the index of its location, otherwise // return -1. func inCoordinates(a [][]int, x []int) int { for idx, n := range a { if x[0] == n[0] && x[1] == n[1] { return idx } } return -1 } // inStrSlice provides a method to check if an element is present in an array, // and return the index of its location, otherwise return -1. func inStrSlice(a []string, x string, caseSensitive bool) int { for idx, n := range a { if !caseSensitive && strings.EqualFold(x, n) { return idx } if x == n { return idx } } return -1 } // inFloat64Slice provides a method to check if an element is present in a // float64 array, and return the index of its location, otherwise return -1. func inFloat64Slice(a []float64, x float64) int { for idx, n := range a { if x == n { return idx } } return -1 } // boolPtr returns a pointer to a bool with the given value. func boolPtr(b bool) *bool { return &b } // intPtr returns a pointer to an int with the given value. func intPtr(i int) *int { return &i } // uintPtr returns a pointer to an unsigned integer with the given value. func uintPtr(u uint) *uint { return &u } // float64Ptr returns a pointer to a float64 with the given value. func float64Ptr(f float64) *float64 { return &f } // stringPtr returns a pointer to a string with the given value. func stringPtr(s string) *string { return &s } // Value extracts float64 data type numeric from a attribute value. func (attr *attrValFloat) Value() float64 { if attr != nil && attr.Val != nil { return *attr.Val } return 0 } // Value extracts boolean data type value from a attribute value. func (avb *attrValBool) Value() bool { if avb != nil && avb.Val != nil { return *avb.Val } return false } // Value extracts string data type text from a attribute value. func (avb *attrValString) Value() string { if avb != nil && avb.Val != nil { return *avb.Val } return "" } // MarshalXML convert the boolean data type to literal values 0 or 1 on // serialization. func (avb attrValBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error { attr := xml.Attr{ Name: xml.Name{ Space: start.Name.Space, Local: "val", }, Value: "0", } if avb.Val != nil { if *avb.Val { attr.Value = "1" } else { attr.Value = "0" } } start.Attr = []xml.Attr{attr} if err := e.EncodeToken(start); err != nil { return err } return e.EncodeToken(start.End()) } // UnmarshalXML convert the literal values true, false, 1, 0 of the XML // attribute to boolean data type on deserialization. func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for { t, err := d.Token() if err != nil { return err } found := false switch t.(type) { case xml.StartElement: return ErrAttrValBool case xml.EndElement: found = true } if found { break } } for _, attr := range start.Attr { if attr.Name.Local == "val" { if attr.Value == "" { val := true avb.Val = &val } else { val, err := strconv.ParseBool(attr.Value) if err != nil { return err } avb.Val = &val } return nil } } defaultVal := true avb.Val = &defaultVal return nil } // MarshalXML encodes ext element with specified namespace attributes on // serialization. func (ext xlsxExt) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Attr = ext.xmlns return e.EncodeElement(decodeExt{URI: ext.URI, Content: ext.Content}, start) } // UnmarshalXML extracts ext element attributes namespace by giving XML decoder // on deserialization. func (ext *xlsxExt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for _, attr := range start.Attr { if attr.Name.Local == "uri" { continue } if attr.Name.Space == "xmlns" { attr.Name.Space = "" attr.Name.Local = "xmlns:" + attr.Name.Local } ext.xmlns = append(ext.xmlns, attr) } e := &decodeExt{} if err := d.DecodeElement(&e, &start); err != nil { return err } ext.URI, ext.Content = e.URI, e.Content return nil } // namespaceStrictToTransitional provides a method to convert Strict and // Transitional namespaces. func namespaceStrictToTransitional(content []byte) []byte { namespaceTranslationDic := map[string]string{ StrictNameSpaceDocumentPropertiesVariantTypes: NameSpaceDocumentPropertiesVariantTypes.Value, StrictNameSpaceDrawingMLMain: NameSpaceDrawingMLMain, StrictNameSpaceExtendedProperties: NameSpaceExtendedProperties, StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet.Value, StrictSourceRelationship: SourceRelationship.Value, StrictSourceRelationshipChart: SourceRelationshipChart, StrictSourceRelationshipComments: SourceRelationshipComments, StrictSourceRelationshipExtendProperties: SourceRelationshipExtendProperties, StrictSourceRelationshipImage: SourceRelationshipImage, StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument, } for s, n := range namespaceTranslationDic { content = bytesReplace(content, []byte(s), []byte(n), -1) } return content } // bytesReplace replace source bytes with given target. func bytesReplace(s, source, target []byte, n int) []byte { if n == 0 { return s } if len(source) < len(target) { return bytes.Replace(s, source, target, n) } if n < 0 { n = len(s) } var wid, i, j, w int for i, j = 0, 0; i < len(s) && j < n; j++ { wid = bytes.Index(s[i:], source) if wid < 0 { break } w += copy(s[w:], s[i:i+wid]) w += copy(s[w:], target) i += wid + len(source) } w += copy(s[w:], s[i:]) return s[:w] } // genSheetPasswd provides a method to generate password for worksheet // protection by given plaintext. When an Excel sheet is being protected with // a password, a 16-bit (two byte) long hash is generated. To verify a // password, it is compared to the hash. Obviously, if the input data volume // is great, numerous passwords will match the same hash. Here is the // algorithm to create the hash value: // // take the ASCII values of all characters shift left the first character 1 bit, // the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, // the highest bit of the 16-bit value is always 0 [signed short]) // XOR all these values // XOR the count of characters // XOR the constant 0xCE4B func genSheetPasswd(plaintext string) string { var password int64 = 0x0000 var charPos uint = 1 for _, v := range plaintext { value := int64(v) << charPos charPos++ rotatedBits := value >> 15 // rotated bits beyond bit 15 value &= 0x7fff // first 15 bits password ^= value | rotatedBits } password ^= int64(len(plaintext)) password ^= 0xCE4B return strings.ToUpper(strconv.FormatInt(password, 16)) } // getRootElement extract root element attributes by given XML decoder. func getRootElement(d *xml.Decoder) []xml.Attr { tokenIdx := 0 for { token, _ := d.Token() if token == nil { break } switch startElement := token.(type) { case xml.StartElement: tokenIdx++ if tokenIdx == 1 { var ns bool for i := 0; i < len(startElement.Attr); i++ { if startElement.Attr[i].Value == NameSpaceSpreadSheet.Value && startElement.Attr[i].Name == NameSpaceSpreadSheet.Name { ns = true } } if !ns { startElement.Attr = append(startElement.Attr, NameSpaceSpreadSheet) } return startElement.Attr } } } return nil } // genXMLNamespace generate serialized XML attributes with a multi namespace // by given element attributes. func genXMLNamespace(attr []xml.Attr) string { var rootElement string for _, v := range attr { if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" { if lastSpace == NameSpaceXML { lastSpace = "xml" } rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value) continue } rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value) } return strings.TrimSpace(rootElement) + ">" } // getXMLNamespace extract XML namespace from specified element name and attributes. func getXMLNamespace(space string, attr []xml.Attr) string { for _, attribute := range attr { if attribute.Value == space { return attribute.Name.Local } } return space } // replaceNameSpaceBytes provides a function to replace the XML root element // attribute by the given component part path and XML content. func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte { sourceXmlns := []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) targetXmlns := []byte(templateNamespaceIDMap) if attrs, ok := f.xmlAttr.Load(path); ok { targetXmlns = []byte(genXMLNamespace(attrs.([]xml.Attr))) } return bytesReplace(contentMarshal, sourceXmlns, bytes.ReplaceAll(targetXmlns, []byte(" mc:Ignorable=\"r\""), []byte{}), -1) } // addNameSpaces provides a function to add an XML attribute by the given // component part path. func (f *File) addNameSpaces(path string, ns xml.Attr) { exist := false mc := false ignore := -1 if attrs, ok := f.xmlAttr.Load(path); ok { for i, attr := range attrs.([]xml.Attr) { if attr.Name.Local == ns.Name.Local && attr.Name.Space == ns.Name.Space { exist = true } if attr.Name.Local == "Ignorable" && getXMLNamespace(attr.Name.Space, attrs.([]xml.Attr)) == "mc" { ignore = i } if attr.Name.Local == "mc" && attr.Name.Space == "xmlns" { mc = true } } } if !exist { attrs, _ := f.xmlAttr.Load(path) if attrs == nil { attrs = []xml.Attr{} } attrs = append(attrs.([]xml.Attr), ns) f.xmlAttr.Store(path, attrs) if !mc { attrs = append(attrs.([]xml.Attr), SourceRelationshipCompatibility) f.xmlAttr.Store(path, attrs) } if ignore == -1 { attrs = append(attrs.([]xml.Attr), xml.Attr{ Name: xml.Name{Local: "Ignorable", Space: "mc"}, Value: ns.Name.Local, }) f.xmlAttr.Store(path, attrs) return } f.setIgnorableNameSpace(path, ignore, ns) } } // setIgnorableNameSpace provides a function to set XML namespace as ignorable // by the given attribute. func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) { ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"} xmlAttrs, _ := f.xmlAttr.Load(path) if inStrSlice(strings.Fields(xmlAttrs.([]xml.Attr)[index].Value), ns.Name.Local, true) == -1 && inStrSlice(ignorableNS, ns.Name.Local, true) != -1 { xmlAttrs.([]xml.Attr)[index].Value = strings.TrimSpace(fmt.Sprintf("%s %s", xmlAttrs.([]xml.Attr)[index].Value, ns.Name.Local)) f.xmlAttr.Store(path, xmlAttrs) } } // addSheetNameSpace add XML attribute for worksheet. func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) { name, _ := f.getSheetXMLPath(sheet) f.addNameSpaces(name, ns) } // isNumeric determines whether an expression is a valid numeric type and get // the precision for the numeric. func isNumeric(s string) (bool, int, float64) { if strings.Contains(s, "_") { return false, 0, 0 } var decimal big.Float _, ok := decimal.SetString(s) if !ok { return false, 0, 0 } var noScientificNotation string flt, _ := decimal.Float64() noScientificNotation = strconv.FormatFloat(flt, 'f', -1, 64) return true, len(strings.ReplaceAll(noScientificNotation, ".", "")), flt } var ( bstrExp = regexp.MustCompile(`_x[a-fA-F\d]{4}_`) bstrEscapeExp = regexp.MustCompile(`x[a-fA-F\d]{4}_`) ) // bstrUnmarshal parses the binary basic string, this will trim escaped string // literal which not permitted in an XML 1.0 document. The basic string // variant type can store any valid Unicode character. Unicode's characters // that cannot be directly represented in XML as defined by the XML 1.0 // specification, shall be escaped using the Unicode numerical character // representation escape character format _xHHHH_, where H represents a // hexadecimal character in the character's value. For example: The Unicode // character 8 is not permitted in an XML 1.0 document, so it shall be // escaped as _x0008_. To store the literal form of an escape sequence, the // initial underscore shall itself be escaped (i.e. stored as _x005F_). For // example: The string literal _x0008_ would be stored as _x005F_x0008_. func bstrUnmarshal(s string) (result string) { matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0 for _, match := range matches { result += s[cursor:match[0]] subStr := s[match[0]:match[1]] if subStr == "_x005F_" { cursor = match[1] result += "_" continue } if bstrExp.MatchString(subStr) { cursor = match[1] v, _ := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`) result += v } } if cursor < l { result += s[cursor:] } return result } // bstrMarshal encode the escaped string literal which not permitted in an XML // 1.0 document. func bstrMarshal(s string) (result string) { matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0 for _, match := range matches { result += s[cursor:match[0]] subStr := s[match[0]:match[1]] if subStr == "_x005F_" { cursor = match[1] if match[1]+6 <= l && bstrEscapeExp.MatchString(s[match[1]:match[1]+6]) { _, err := strconv.Unquote(`"\u` + s[match[1]+1:match[1]+5] + `"`) if err == nil { result += subStr + "x005F" + subStr continue } } result += subStr + "x005F_" continue } if bstrExp.MatchString(subStr) { cursor = match[1] if _, err := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`); err == nil { result += "_x005F" + subStr continue } } } if cursor < l { result += s[cursor:] } return result } // floatToFraction converts a float number to a fraction string representation // with specified placeholder widths for numerator and denominator. func floatToFraction(x float64, numeratorPlaceHolder, denominatorPlaceHolder int) string { if denominatorPlaceHolder <= 0 { return "" } num, den := floatToFracUseContinuedFraction(x, int64(math.Pow10(denominatorPlaceHolder))) if num == 0 { return strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1) } numStr := strconv.FormatInt(num, 10) denStr := strconv.FormatInt(den, 10) numeratorPlaceHolder = max(numeratorPlaceHolder-len(numStr), 0) denominatorPlaceHolder = max(denominatorPlaceHolder-len(denStr), 0) return fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), numStr, denStr, strings.Repeat(" ", denominatorPlaceHolder)) } // floatToFracUseContinuedFraction implement convert a floating-point decimal // to a fraction using continued fractions and recurrence relations. func floatToFracUseContinuedFraction(r float64, denominatorLimit int64) (num, den int64) { p1 := int64(1) // LaTex: p_{-1} q1 := int64(0) // LaTex: q_{-1} p2 := int64(0) // LaTex: p_{-2} q2 := int64(1) // LaTex: q_{-2} var lasta, lastb int64 var curra, currb int64 for k := 0; ; k++ { // a_{k} = \lfloor r_{k} \rfloor a := int64(math.Floor(r)) // Fundamental recurrence formulas: p_{k} = a_{k} \cdot p_{k-1} + p_{k-2} curra, currb = a*p1+p2, a*q1+q2 p2 = p1 q2 = q1 p1 = curra q1 = currb frac := r - float64(a) if q1 >= denominatorLimit { return lasta, lastb } if math.Abs(frac) < 1e-12 { // use safe floating-point number comparison. If the input(r) is a real number, here is 0. return curra, currb } lasta, lastb = curra, currb // r_{k+1} = \frac{1}{r_{k} - a_{k}} r = 1.0 / frac } } // assignFieldValue assigns the value from an immutable reflect.Value to a // mutable reflect.Value based on the type of the immutable value. func assignFieldValue(field string, immutable, mutable reflect.Value) { switch immutable.Kind() { case reflect.Bool: mutable.FieldByName(field).SetBool(immutable.Bool()) case reflect.Int: mutable.FieldByName(field).SetInt(immutable.Int()) default: mutable.FieldByName(field).SetString(immutable.String()) } } // setPtrFields assigns the fields of the immutable struct to the mutable // struct. The fields name of the immutable struct must match the field names of // the mutable struct. func setPtrFields(immutable, mutable reflect.Value) { for i := range immutable.NumField() { srcField := immutable.Type().Field(i) dstField := mutable.FieldByName(srcField.Name) if dstField.IsValid() && dstField.CanSet() && dstField.Type() == immutable.Field(i).Type() { dstField.Set(immutable.Field(i)) } } } // setNoPtrFieldsVal assigns values from the pointer or no-pointer structs // fields (immutable) value to no-pointer struct field. func setNoPtrFieldsVal(fields []string, immutable, mutable reflect.Value) { for _, field := range fields { immutableField := immutable.FieldByName(field) if immutableField.Kind() == reflect.Ptr { if immutableField.IsValid() && !immutableField.IsNil() { assignFieldValue(field, immutableField.Elem(), mutable) } continue } assignFieldValue(field, immutableField, mutable) } } // setPtrFieldsVal assigns values from the pointer or no-pointer structs // fields (immutable) value to pointer struct field. func setPtrFieldsVal(fields []string, immutable, mutable reflect.Value) { for _, field := range fields { immutableField := immutable.FieldByName(field) if immutableField.Kind() == reflect.Ptr { if immutableField.IsValid() && !immutableField.IsNil() { mutable.FieldByName(field).Set(immutableField.Elem()) } continue } if immutableField.IsZero() { continue } ptr := reflect.New(immutableField.Type()) ptr.Elem().Set(immutableField) mutable.FieldByName(field).Set(ptr) } } // countUTF16String returns the number of UTF-16 code units in a string. func countUTF16String(s string) int { var cnt int for _, r := range s { cnt += utf16.RuneLen(r) } return cnt } // truncateUTF16Units truncates a string to a maximum number of UTF-16 code // units. func truncateUTF16Units(s string, length int) string { var cnt int for i, r := range s { if cnt += utf16.RuneLen(r); cnt > length { return s[:i] } } return s } // Stack defined an abstract data type that serves as a collection of elements. type Stack struct { list *list.List } // NewStack create a new stack. func NewStack() *Stack { l := list.New() return &Stack{l} } // Push a value onto the top of the stack. func (stack *Stack) Push(value interface{}) { stack.list.PushBack(value) } // Pop the top item of the stack and return it. func (stack *Stack) Pop() interface{} { e := stack.list.Back() if e != nil { stack.list.Remove(e) return e.Value } return nil } // Peek view the top item on the stack. func (stack *Stack) Peek() interface{} { e := stack.list.Back() if e != nil { return e.Value } return nil } // Len return the number of items in the stack. func (stack *Stack) Len() int { return stack.list.Len() } // Empty the stack. func (stack *Stack) Empty() bool { return stack.list.Len() == 0 } ================================================ FILE: lib_test.go ================================================ package excelize import ( "archive/zip" "bytes" "encoding/xml" "fmt" "io" "math" "os" "strconv" "strings" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var validColumns = []struct { Name string Num int }{ {Name: "A", Num: 1}, {Name: "Z", Num: 26}, {Name: "AA", Num: 26 + 1}, {Name: "AK", Num: 26 + 11}, {Name: "ak", Num: 26 + 11}, {Name: "Ak", Num: 26 + 11}, {Name: "aK", Num: 26 + 11}, {Name: "AZ", Num: 26 + 26}, {Name: "ZZ", Num: 26 + 26*26}, {Name: "AAA", Num: 26 + 26*26 + 1}, } var invalidColumns = []struct { Name string Num int }{ {Name: "", Num: -1}, {Name: " ", Num: -1}, {Name: "_", Num: -1}, {Name: "__", Num: -1}, {Name: "-1", Num: -1}, {Name: "0", Num: -1}, {Name: " A", Num: -1}, {Name: "A ", Num: -1}, {Name: "A1", Num: -1}, {Name: "1A", Num: -1}, {Name: " a", Num: -1}, {Name: "a ", Num: -1}, {Name: "a1", Num: -1}, {Name: "1a", Num: -1}, {Name: " _", Num: -1}, {Name: "_ ", Num: -1}, {Name: "_1", Num: -1}, {Name: "1_", Num: -1}, } var invalidCells = []string{"", "A", "AA", " A", "A ", "1A", "A1A", "A1 ", " A1", "1A1", "a-1", "A-1"} var invalidIndexes = []int{-100, -2, -1, 0} func TestColumnNameToNumber_OK(t *testing.T) { const msg = "Column %q" for _, col := range validColumns { out, err := ColumnNameToNumber(col.Name) if assert.NoErrorf(t, err, msg, col.Name) { assert.Equalf(t, col.Num, out, msg, col.Name) } } } func TestColumnNameToNumber_Error(t *testing.T) { const msg = "Column %q" for _, col := range invalidColumns { out, err := ColumnNameToNumber(col.Name) if assert.Errorf(t, err, msg, col.Name) { assert.Equalf(t, col.Num, out, msg, col.Name) } } _, err := ColumnNameToNumber("XFE") assert.ErrorIs(t, err, ErrColumnNumber) } func TestColumnNumberToName_OK(t *testing.T) { const msg = "Column %q" for _, col := range validColumns { out, err := ColumnNumberToName(col.Num) if assert.NoErrorf(t, err, msg, col.Name) { assert.Equalf(t, strings.ToUpper(col.Name), out, msg, col.Name) } } } func TestColumnNumberToName_Error(t *testing.T) { out, err := ColumnNumberToName(-1) if assert.Error(t, err) { assert.Empty(t, out) } out, err = ColumnNumberToName(0) if assert.Error(t, err) { assert.Empty(t, out) } _, err = ColumnNumberToName(MaxColumns + 1) assert.ErrorIs(t, err, ErrColumnNumber) } func TestSplitCellName_OK(t *testing.T) { const msg = "Cell \"%s%d\"" for i, col := range validColumns { row := i + 1 c, r, err := SplitCellName(col.Name + strconv.Itoa(row)) if assert.NoErrorf(t, err, msg, col.Name, row) { assert.Equalf(t, col.Name, c, msg, col.Name, row) assert.Equalf(t, row, r, msg, col.Name, row) } } } func TestSplitCellName_Error(t *testing.T) { const msg = "Cell %q" for _, cell := range invalidCells { c, r, err := SplitCellName(cell) if assert.Errorf(t, err, msg, cell) { assert.Equalf(t, "", c, msg, cell) assert.Equalf(t, -1, r, msg, cell) } } } func TestJoinCellName_OK(t *testing.T) { const msg = "Cell \"%s%d\"" for i, col := range validColumns { row := i + 1 cell, err := JoinCellName(col.Name, row) if assert.NoErrorf(t, err, msg, col.Name, row) { assert.Equalf(t, strings.ToUpper(fmt.Sprintf("%s%d", col.Name, row)), cell, msg, row) } } } func TestJoinCellName_Error(t *testing.T) { const msg = "Cell \"%s%d\"" test := func(col string, row int) { cell, err := JoinCellName(col, row) if assert.Errorf(t, err, msg, col, row) { assert.Equalf(t, "", cell, msg, col, row) } } for _, col := range invalidColumns { test(col.Name, 1) for _, row := range invalidIndexes { test("A", row) test(col.Name, row) } } } func TestCellNameToCoordinates_OK(t *testing.T) { const msg = "Cell \"%s%d\"" for i, col := range validColumns { row := i + 1 c, r, err := CellNameToCoordinates(col.Name + strconv.Itoa(row)) if assert.NoErrorf(t, err, msg, col.Name, row) { assert.Equalf(t, col.Num, c, msg, col.Name, row) assert.Equalf(t, i+1, r, msg, col.Name, row) } } } func TestCellNameToCoordinates_Error(t *testing.T) { const msg = "Cell %q" for _, cell := range invalidCells { c, r, err := CellNameToCoordinates(cell) if assert.Errorf(t, err, msg, cell) { assert.Equalf(t, -1, c, msg, cell) assert.Equalf(t, -1, r, msg, cell) } } _, _, err := CellNameToCoordinates("A1048577") assert.EqualError(t, err, ErrMaxRows.Error()) } func TestCoordinatesToCellName_OK(t *testing.T) { const msg = "Coordinates [%d, %d]" for i, col := range validColumns { row := i + 1 cell, err := CoordinatesToCellName(col.Num, row) if assert.NoErrorf(t, err, msg, col.Num, row) { assert.Equalf(t, strings.ToUpper(col.Name+strconv.Itoa(row)), cell, msg, col.Num, row) } } } func TestCoordinatesToCellName_Error(t *testing.T) { const msg = "Coordinates [%d, %d]" test := func(col, row int) { cell, err := CoordinatesToCellName(col, row) if assert.Errorf(t, err, msg, col, row) { assert.Equalf(t, "", cell, msg, col, row) } } for _, col := range invalidIndexes { test(col, 1) for _, row := range invalidIndexes { test(1, row) test(col, row) } } } func TestCoordinatesToRangeRef(t *testing.T) { _, err := coordinatesToRangeRef([]int{}) assert.EqualError(t, err, ErrCoordinates.Error()) _, err = coordinatesToRangeRef([]int{1, -1, 1, 1}) assert.Equal(t, newCoordinatesToCellNameError(1, -1), err) _, err = coordinatesToRangeRef([]int{1, 1, 1, -1}) assert.Equal(t, newCoordinatesToCellNameError(1, -1), err) ref, err := coordinatesToRangeRef([]int{1, 1, 1, 1}) assert.NoError(t, err) assert.EqualValues(t, ref, "A1:A1") } func TestSortCoordinates(t *testing.T) { assert.EqualError(t, sortCoordinates(make([]int, 3)), ErrCoordinates.Error()) } func TestInStrSlice(t *testing.T) { assert.EqualValues(t, -1, inStrSlice([]string{}, "", true)) } func TestAttrValue(t *testing.T) { assert.Empty(t, (&attrValString{}).Value()) assert.False(t, (&attrValBool{}).Value()) assert.Zero(t, (&attrValFloat{}).Value()) } func TestBoolValMarshal(t *testing.T) { bold := true node := &xlsxFont{B: &attrValBool{Val: &bold}} data, err := xml.Marshal(node) assert.NoError(t, err) assert.Equal(t, ``, string(data)) node = &xlsxFont{} err = xml.Unmarshal(data, node) assert.NoError(t, err) assert.NotEqual(t, nil, node) assert.NotEqual(t, nil, node.B) assert.NotEqual(t, nil, node.B.Val) assert.Equal(t, true, *node.B.Val) } func TestBoolValUnmarshalXML(t *testing.T) { node := xlsxFont{} assert.NoError(t, xml.Unmarshal([]byte(""), &node)) assert.Equal(t, true, *node.B.Val) for content, err := range map[string]string{ "": "unexpected child of attrValBool", "": "strconv.ParseBool: parsing \"x\": invalid syntax", } { assert.EqualError(t, xml.Unmarshal([]byte(content), &node), err) } attr := attrValBool{} assert.EqualError(t, attr.UnmarshalXML(xml.NewDecoder(strings.NewReader("")), xml.StartElement{}), io.EOF.Error()) } func TestExtUnmarshalXML(t *testing.T) { f, extLst := NewFile(), decodeExtLst{} expected := fmt.Sprintf(``, ExtURISlicerCachesX14, NameSpaceSpreadSheetX14.Value) assert.NoError(t, f.xmlNewDecoder(strings.NewReader(expected)).Decode(&extLst)) assert.Len(t, extLst.Ext, 1) assert.Equal(t, extLst.Ext[0].URI, ExtURISlicerCachesX14) } func TestBytesReplace(t *testing.T) { s := []byte{0x01} assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0)) } func TestGetRootElement(t *testing.T) { assert.Len(t, getRootElement(xml.NewDecoder(strings.NewReader(""))), 0) // Test get workbook root element which all workbook XML namespace has prefix f := NewFile() d := f.xmlNewDecoder(bytes.NewReader([]byte(``))) assert.Len(t, getRootElement(d), 3) } func TestSetIgnorableNameSpace(t *testing.T) { f := NewFile() f.xmlAttr.Store("xml_path", []xml.Attr{{}}) f.setIgnorableNameSpace("xml_path", 0, xml.Attr{Name: xml.Name{Local: "c14"}}) attrs, ok := f.xmlAttr.Load("xml_path") assert.EqualValues(t, "c14", attrs.([]xml.Attr)[0].Value) assert.True(t, ok) } func TestStack(t *testing.T) { s := NewStack() assert.Equal(t, s.Peek(), nil) assert.Equal(t, s.Pop(), nil) } func TestGenXMLNamespace(t *testing.T) { assert.Equal(t, genXMLNamespace([]xml.Attr{ {Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve"}, }), `xml:space="preserve">`) } func TestBstrUnmarshal(t *testing.T) { bstrs := map[string]string{ "*": "*", "*_x0000_": "*\x00", "*_x0008_": "*\b", "_x0008_*": "\b*", "*_x0008_*": "*\b*", "*_x4F60__x597D_": "*你好", "*_xG000_": "*_xG000_", "*_xG05F_x0001_*": "*_xG05F\x01*", "*_x005F__x0008_*": "*_\b*", "*_x005F_x0001_*": "*_x0001_*", "*_x005f_x005F__x0008_*": "*_x005F_\b*", "*_x005F_x005F_xG05F_x0006_*": "*_x005F_xG05F\x06*", "*_x005F_x005F_x005F_x0006_*": "*_x005F_x0006_*", "_x005F__x0008_******": "_\b******", "******_x005F__x0008_": "******_\b", "******_x005F__x0008_******": "******_\b******", "_x000x_x005F_x000x_": "_x000x_x000x_", } for bstr, expected := range bstrs { assert.Equal(t, expected, bstrUnmarshal(bstr), bstr) } } func TestBstrMarshal(t *testing.T) { bstrs := map[string]string{ "*_xG05F_*": "*_xG05F_*", "*_x0008_*": "*_x005F_x0008_*", "*_x005F_*": "*_x005F_x005F_*", "*_x005F_xG006_*": "*_x005F_x005F_xG006_*", "*_x005F_x0006_*": "*_x005F_x005F_x005F_x0006_*", } for bstr, expected := range bstrs { assert.Equal(t, expected, bstrMarshal(bstr)) } } func TestTruncateUTF16Units(t *testing.T) { assertTrunc := func(s string, max int, expected string) { assert.Equal(t, expected, truncateUTF16Units(s, max), "src=%q max=%d", s, max) assert.LessOrEqual(t, countUTF16String(truncateUTF16Units(s, max)), max) } // No truncation assertTrunc("ABC", 3, "ABC") assertTrunc("A\U0001F600B", 4, "A\U0001F600B") // Truncate cutting before BMP rune assertTrunc("ABCDE", 3, "ABC") // Truncate with surrogate pair boundary: keep pair intact assertTrunc("A\U0001F600B", 3, "A\U0001F600") // 1 + 2 units assertTrunc("A\U0001F600B", 2, "A") // pair would overflow assertTrunc("\U0001F600B", 1, "") // first rune (2 units) exceeds limit assertTrunc("\U0001F600B", 2, "\U0001F600") // exact fit assertTrunc("\U0001F600B", 3, "\U0001F600B") // allow extra // Multiple surrogate pairs assertTrunc("\U0001F600\U0001F600B", 2, "\U0001F600") // corrected expectation per logic assertTrunc("\U0001F600\U0001F600B", 3, "\U0001F600") // 2 units kept, next pair would exceed assertTrunc("\U0001F600\U0001F600B", 4, "\U0001F600\U0001F600") // both pairs (4 units) } func TestReadBytes(t *testing.T) { f := &File{tempFiles: sync.Map{}} sheet := "xl/worksheets/sheet1.xml" f.tempFiles.Store(sheet, "/d/") assert.Equal(t, []byte{}, f.readBytes(sheet)) } func TestUnzipToTemp(t *testing.T) { assert.NoError(t, os.Setenv("TMPDIR", "test")) defer func() { assert.NoError(t, os.Unsetenv("TMPDIR")) }() assert.NoError(t, os.Chmod(os.TempDir(), 0o444)) f := NewFile() data := []byte("PK\x03\x040000000PK\x01\x0200000" + "0000000000000000000\x00" + "\x00\x00\x00\x00\x00000000000000PK\x01" + "\x020000000000000000000" + "00000\v\x00\x00\x00\x00\x00000000000" + "00000000000000PK\x01\x0200" + "00000000000000000000" + "00\v\x00\x00\x00\x00\x00000000000000" + "00000000000PK\x01\x020000<" + "0\x00\x0000000000000000\v\x00\v" + "\x00\x00\x00\x00\x0000000000\x00\x00\x00\x00000" + "00000000PK\x01\x0200000000" + "0000000000000000\v\x00\x00\x00" + "\x00\x0000PK\x05\x06000000\x05\x00\xfd\x00\x00\x00" + "\v\x00\x00\x00\x00\x00") z, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) assert.NoError(t, err) _, err = f.unzipToTemp(z.File[0]) require.Error(t, err) assert.NoError(t, os.Chmod(os.TempDir(), 0o755)) _, err = f.unzipToTemp(z.File[0]) assert.EqualError(t, err, "EOF") } func TestFloat2Frac(t *testing.T) { assert.Empty(t, floatToFraction(0.19, 0, 0)) assert.Equal(t, "1/5", floatToFraction(0.19, 1, 1)) assert.Equal(t, "9999/10000", strings.Trim(floatToFraction(0.9999, 10, 10), " ")) assert.Equal(t, "954888175898973913/351283728530932463", floatToFraction(math.E, 1, 18)) } ================================================ FILE: merge.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "strings" // Rect gets merged cell rectangle coordinates sequence. func (mc *xlsxMergeCell) Rect() ([]int, error) { var err error if mc.rect == nil { mergedCellsRef := mc.Ref if !strings.Contains(mergedCellsRef, ":") { mergedCellsRef += ":" + mergedCellsRef } mc.rect, err = rangeRefToCoordinates(mergedCellsRef) } return mc.rect, err } // MergeCell provides a function to merge cells by given range reference and // sheet name. Merging cells only keeps the upper-left cell value, and // discards the other values. For example create a merged cell of D3:E9 on // Sheet1: // // err := f.MergeCell("Sheet1", "D3", "E9") // // If you create a merged cell that overlaps with another existing merged cell, // those merged cells that already exist will be removed. The cell references // tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1) // A8(x3,y4) D8(x2,y4) // // B1(x1,y1) D1(x2,y1) // +------------------------+ // | | // A4(x3,y3) | C4(x4,y3) | // +------------------------+ | // | | | | // | |B5(x1,y2) | D5(x2,y2)| // | +------------------------+ // | | // |A8(x3,y4) C8(x4,y4)| // +------------------------+ func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error { rect, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell) if err != nil { return err } // Correct the range reference, such correct C1:B3 to B1:C3. _ = sortCoordinates(rect) topLeftCell, _ = CoordinatesToCellName(rect[0], rect[1]) bottomRightCell, _ = CoordinatesToCellName(rect[2], rect[3]) ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.mu.Lock() defer ws.mu.Unlock() for col := rect[0]; col <= rect[2]; col++ { for row := rect[1]; row <= rect[3]; row++ { if col == rect[0] && row == rect[1] { continue } ws.prepareSheetXML(col, row) c := &ws.SheetData.Row[row-1].C[col-1] c.setCellDefault("") _ = f.removeFormula(c, ws, sheet) } } ref := topLeftCell + ":" + bottomRightCell if ws.MergeCells != nil { ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect}) } else { ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}} } ws.MergeCells.Count = len(ws.MergeCells.Cells) return err } // UnmergeCell provides a function to unmerge a given range reference. // For example unmerge range reference D3:E9 on Sheet1: // // err := f.UnmergeCell("Sheet1", "D3", "E9") // // Attention: overlapped range will also be unmerged. func (f *File) UnmergeCell(sheet, topLeftCell, bottomRightCell string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.mu.Lock() defer ws.mu.Unlock() rect1, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell) if err != nil { return err } // Correct the range reference, such correct C1:B3 to B1:C3. _ = sortCoordinates(rect1) // return nil since no MergeCells in the sheet if ws.MergeCells == nil { return nil } if err = ws.mergeOverlapCells(); err != nil { return err } f.clearCalcCache() i := 0 for _, mergeCell := range ws.MergeCells.Cells { if rect2, _ := rangeRefToCoordinates(mergeCell.Ref); isOverlap(rect1, rect2) { continue } ws.MergeCells.Cells[i] = mergeCell i++ } ws.MergeCells.Cells = ws.MergeCells.Cells[:i] ws.MergeCells.Count = len(ws.MergeCells.Cells) if ws.MergeCells.Count == 0 { ws.MergeCells = nil } return nil } // GetMergeCells provides a function to get all merged cells from a specific // worksheet. If the `withoutValues` parameter is set to true, it will not // return the cell values of merged cells, only the range reference will be // returned. For example get all merged cells on Sheet1: // // mergeCells, err := f.GetMergeCells("Sheet1") // // If you want to get merged cells without cell values, you can use the // following code: // // mergeCells, err := f.GetMergeCells("Sheet1", true) func (f *File) GetMergeCells(sheet string, withoutValues ...bool) ([]MergeCell, error) { var ( mergeCells []MergeCell withoutVal bool ) if len(withoutValues) > 0 { withoutVal = withoutValues[0] } ws, err := f.workSheetReader(sheet) if err != nil { return mergeCells, err } if ws.MergeCells != nil { if err = ws.mergeOverlapCells(); err != nil { return mergeCells, err } mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells)) for i := range ws.MergeCells.Cells { ref, val := ws.MergeCells.Cells[i].Ref, "" if !withoutVal { cell := strings.Split(ref, ":")[0] val, _ = f.GetCellValue(sheet, cell) } mergeCells = append(mergeCells, []string{ref, val}) } } return mergeCells, err } // mergeOverlapCells merge overlap cells. func (ws *xlsxWorksheet) mergeOverlapCells() error { var ( err error rectList [][]int merged = true ) for _, cell := range ws.MergeCells.Cells { if cell == nil { continue } if cell.rect, err = cell.Rect(); err != nil { return err } rectList = append(rectList, cell.rect) } for merged { merged = false var mergedRectList [][]int used := make([]bool, len(rectList)) for i := 0; i < len(rectList); i++ { if used[i] { continue } r1 := rectList[i] for j := i + 1; j < len(rectList); j++ { if used[j] { continue } if r2 := rectList[j]; isOverlap(r1, r2) { r1 = []int{ min(r1[0], r2[0]), min(r1[1], r2[1]), max(r1[2], r2[2]), max(r1[3], r2[3]), } merged, used[j] = true, true } } mergedRectList = append(mergedRectList, r1) } rectList = mergedRectList } ws.MergeCells.Cells = make([]*xlsxMergeCell, 0, len(rectList)) for _, r := range rectList { ref, _ := coordinatesToRangeRef(r) ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: r}) } ws.MergeCells.Count = len(ws.MergeCells.Cells) return err } // MergeCell define a merged cell data. // It consists of the following structure. // example: []string{"D4:E10", "cell value"} type MergeCell []string // GetCellValue returns merged cell value. func (m *MergeCell) GetCellValue() string { return (*m)[1] } // GetStartAxis returns the top left cell reference of merged range, for // example: "C2". func (m *MergeCell) GetStartAxis() string { return strings.Split((*m)[0], ":")[0] } // GetEndAxis returns the bottom right cell reference of merged range, for // example: "D4". func (m *MergeCell) GetEndAxis() string { return strings.Split((*m)[0], ":")[1] } ================================================ FILE: merge_test.go ================================================ package excelize import ( "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestMergeCell(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } assert.EqualError(t, f.MergeCell("Sheet1", "A", "B"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) for _, cells := range [][]string{ {"D9", "D9"}, {"D9", "E9"}, {"H14", "G13"}, {"C9", "D8"}, {"F11", "G13"}, {"H7", "B15"}, {"D11", "F13"}, {"G10", "K12"}, } { assert.NoError(t, f.MergeCell("Sheet1", cells[0], cells[1])) } assert.NoError(t, f.SetCellValue("Sheet1", "G11", "set value in merged cell")) assert.NoError(t, f.SetCellInt("Sheet1", "H11", 100)) assert.NoError(t, f.SetCellValue("Sheet1", "I11", 0.5)) assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/xuri/excelize", "External")) assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)")) value, err := f.GetCellValue("Sheet1", "H11") assert.Equal(t, "100", value) assert.NoError(t, err) // Merged cell ref is single coordinate value, err = f.GetCellValue("Sheet2", "A6") assert.Empty(t, value) assert.NoError(t, err) value, err = f.GetCellFormula("Sheet1", "G12") assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value) assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) for _, cells := range [][]string{ {"D11", "F13"}, {"G10", "K12"}, {"B1", "D5"}, // B1:D5 {"E1", "F5"}, // E1:F5 {"H2", "I5"}, {"I4", "J6"}, // H2:J6 {"M2", "N5"}, {"L4", "M6"}, // L2:N6 {"P4", "Q7"}, {"O2", "P5"}, // O2:Q7 {"A9", "B12"}, {"B7", "C9"}, // A7:C12 {"E9", "F10"}, {"D8", "G12"}, {"I8", "I12"}, {"I10", "K10"}, {"M8", "Q13"}, {"N10", "O11"}, } { assert.NoError(t, f.MergeCell("Sheet3", cells[0], cells[1])) } // Test merge cells on not exists worksheet assert.EqualError(t, f.MergeCell("SheetN", "N10", "O11"), "sheet SheetN does not exist") // Test merged cells with invalid sheet name assert.EqualError(t, f.MergeCell("Sheet:1", "N10", "O11"), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCell.xlsx"))) assert.NoError(t, f.Close()) f = NewFile() assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}} assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) // Test getting merged cells with the same start and end axis ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}} mergedCells, err := f.GetMergeCells("Sheet1") assert.NoError(t, err) assert.Equal(t, "A1", mergedCells[0].GetStartAxis()) assert.Equal(t, "A1", mergedCells[0].GetEndAxis()) assert.Empty(t, mergedCells[0].GetCellValue()) } func TestMergeCellOverlap(t *testing.T) { f := NewFile() assert.NoError(t, f.MergeCell("Sheet1", "A1", "C2")) assert.NoError(t, f.MergeCell("Sheet1", "B2", "D3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCellOverlap.xlsx"))) f, err := OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx")) if !assert.NoError(t, err) { t.FailNow() } mc, err := f.GetMergeCells("Sheet1") assert.NoError(t, err) assert.Len(t, mc, 1) assert.Equal(t, "A1", mc[0].GetStartAxis()) assert.Equal(t, "D3", mc[0].GetEndAxis()) assert.Empty(t, mc[0].GetCellValue()) assert.NoError(t, f.Close()) } func TestGetMergeCells(t *testing.T) { wants := []struct { value string start string end string }{{ value: "A1", start: "A1", end: "B1", }, { value: "A2", start: "A2", end: "A3", }, { value: "A4", start: "A4", end: "B5", }, { value: "A7", start: "A7", end: "C10", }} f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx")) if !assert.NoError(t, err) { t.FailNow() } sheet1 := f.GetSheetName(0) mergeCells, err := f.GetMergeCells(sheet1) assert.NoError(t, err) assert.Len(t, mergeCells, len(wants)) for i, m := range mergeCells { assert.Equal(t, wants[i].value, m.GetCellValue()) assert.Equal(t, wants[i].start, m.GetStartAxis()) assert.Equal(t, wants[i].end, m.GetEndAxis()) } // Test get merged cells without cell values mergeCells, err = f.GetMergeCells(sheet1, true) assert.NoError(t, err) assert.Len(t, mergeCells, len(wants)) for i, m := range mergeCells { assert.Empty(t, m.GetCellValue()) assert.Equal(t, wants[i].start, m.GetStartAxis()) assert.Equal(t, wants[i].end, m.GetEndAxis()) } // Test get merged cells with invalid sheet name _, err = f.GetMergeCells("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test get merged cells on not exists worksheet _, err = f.GetMergeCells("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) } func TestUnmergeCell(t *testing.T) { f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx")) if !assert.NoError(t, err) { t.FailNow() } sheet1 := f.GetSheetName(0) sheet, err := f.workSheetReader(sheet1) assert.NoError(t, err) mergeCellNum := len(sheet.MergeCells.Cells) assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test unmerge the merged cells that contains A1 assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1")) if len(sheet.MergeCells.Cells) != mergeCellNum-1 { t.FailNow() } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnmergeCell.xlsx"))) assert.NoError(t, f.Close()) f = NewFile() assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) // Test unmerged range reference on not exists worksheet assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN does not exist") // Test unmerge the merged cells with invalid sheet name assert.EqualError(t, f.UnmergeCell("Sheet:1", "A1", "A1"), ErrSheetNameInvalid.Error()) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = nil assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15")) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}} assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7")) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}} assert.NoError(t, f.UnmergeCell("Sheet1", "A2", "B3")) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestMergeCellsParser(t *testing.T) { ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{nil}}} _, err := ws.mergeCellsParser("A1") assert.NoError(t, err) } ================================================ FILE: numfmt.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "fmt" "math" "math/big" "strconv" "strings" "time" "github.com/xuri/nfp" ) // languageInfo defined the required fields of localization support for number // format. type languageInfo struct { apFmt string tags, weekdayNames, weekdayNamesAbbr []string useGannen bool localMonth func(t time.Time, abbr int) string } // numberFormat directly maps the number format parser runtime required // fields. type numberFormat struct { opts *Options cellType CellType section []nfp.Section t time.Time sectionIdx int date1904, isNumeric, hours, seconds, useMillisecond, useGannen bool number float64 ap, localCode, result, value, valueSectionType string switchArgument, currencyString string fracHolder, fracPadding, intHolder, intPadding, expBaseLen int percent int useCommaSep, useFraction, usePointer, usePositive, useScientificNotation bool } // CultureName is the type of supported language country codes types for apply // number format. type CultureName byte // This section defines the currently supported country code types enumeration // for apply number format. const ( CultureNameUnknown CultureName = iota CultureNameEnUS CultureNameJaJP CultureNameKoKR CultureNameZhCN CultureNameZhTW ) var ( // Excel styles can reference number formats that are built-in, all of which // have an id less than 164. Note that this number format code list is under // English localization. builtInNumFmt = map[int]string{ 0: "general", 1: "0", 2: "0.00", 3: "#,##0", 4: "#,##0.00", 9: "0%", 10: "0.00%", 11: "0.00E+00", 12: "# ?/?", 13: "# ??/??", 14: "mm-dd-yy", 15: "d-mmm-yy", 16: "d-mmm", 17: "mmm-yy", 18: "h:mm AM/PM", 19: "h:mm:ss AM/PM", 20: "hh:mm", 21: "hh:mm:ss", 22: "m/d/yy hh:mm", 37: "#,##0 ;(#,##0)", 38: "#,##0 ;[red](#,##0)", 39: "#,##0.00 ;(#,##0.00)", 40: "#,##0.00 ;[red](#,##0.00)", 41: "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)", 42: "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)", 43: "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)", 44: "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)", 45: "mm:ss", 46: "[h]:mm:ss", 47: "mm:ss.0", 48: "##0.0E+0", 49: "@", } // langNumFmt defined number format code provided for language glyphs where // they occur in different language. langNumFmt = map[string]map[int]string{ "zh-tw": { 27: "[$-404]e/m/d", 28: "[$-404]e\"年\"m\"月\"d\"日\"", 29: "[$-404]e\"年\"m\"月\"d\"日\"", 30: "m/d/yy", 31: "yyyy\"年\"m\"月\"d\"日\"", 32: "hh\"時\"mm\"分\"", 33: "hh\"時\"mm\"分\"ss\"秒\"", 34: "上午/下午hh\"時\"mm\"分\"", 35: "上午/下午hh\"時\"mm\"分\"ss\"秒\"", 36: "[$-404]e/m/d", 50: "[$-404]e/m/d", 51: "[$-404]e\"年\"m\"月\"d\"日\"", 52: "上午/下午hh\"時\"mm\"分\"", 53: "上午/下午hh\"時\"mm\"分\"ss\"秒\"", 54: "[$-404]e\"年\"m\"月\"d\"日\"", 55: "上午/下午hh\"時\"mm\"分\"", 56: "上午/下午hh\"時\"mm\"分\"ss\"秒\"", 57: "[$-404]e/m/d", 58: "[$-404]e\"年\"m\"月\"d\"日\"", }, "zh-cn": { 27: "yyyy\"年\"m\"月\"", 28: "m\"月\"d\"日\"", 29: "m\"月\"d\"日\"", 30: "m/d/yy", 31: "yyyy\"年\"m\"月\"d\"日\"", 32: "h\"时\"mm\"分\"", 33: "h\"时\"mm\"分\"ss\"秒\"", 34: "上午/下午h\"时\"mm\"分\"", 35: "上午/下午h\"时\"mm\"分\"ss\"秒\"", 36: "yyyy\"年\"m\"月\"", 50: "yyyy\"年\"m\"月\"", 51: "m\"月\"d\"日\"", 52: "yyyy\"年\"m\"月\"", 53: "m\"月\"d\"日\"", 54: "m\"月\"d\"日\"", 55: "上午/下午h\"时\"mm\"分\"", 56: "上午/下午h\"时\"mm\"分\"ss\"秒\"", 57: "yyyy\"年\"m\"月\"", 58: "m\"月\"d\"日\"", }, "ja-jp": { 27: "[$-411]ge.m.d", 28: "[$-411]ggge\"年\"m\"月\"d\"日\"", 29: "[$-411]ggge\"年\"m\"月\"d\"日\"", 30: "m/d/yy", 31: "yyyy\"年\"m\"月\"d\"日\"", 32: "h\"時\"mm\"分\"", 33: "h\"時\"mm\"分\"ss\"秒\"", 34: "yyyy\"年\"m\"月\"", 35: "m\"月\"d\"日\"", 36: "[$-411]ge.m.d", 50: "[$-411]ge.m.d", 51: "[$-411]ggge\"年\"m\"月\"d\"日\"", 52: "yyyy\"年\"m\"月\"", 53: "m\"月\"d\"日\"", 54: "[$-411]ggge\"年\"m\"月\"d\"日\"", 55: "yyyy\"年\"m\"月\"", 56: "m\"月\"d\"日\"", 57: "[$-411]ge.m.d", 58: "[$-411]ggge\"年\"m\"月\"d\"日\"", }, "ko-kr": { 27: "yyyy\"年\" mm\"月\" dd\"日\"", 28: "mm-dd", 29: "mm-dd", 30: "mm-dd-yy", 31: "yyyy\"년\" mm\"월\" dd\"일\"", 32: "h\"시\" mm\"분\"", 33: "h\"시\" mm\"분\" ss\"초\"", 34: "yyyy-mm-dd", 35: "yyyy-mm-dd", 36: "yyyy\"年\" mm\"月\" dd\"日\"", 50: "yyyy\"年\" mm\"月\" dd\"日\"", 51: "mm-dd", 52: "yyyy-mm-dd", 53: "yyyy-mm-dd", 54: "mm-dd", 55: "yyyy-mm-dd", 56: "yyyy-mm-dd", 57: "yyyy\"年\" mm\"月\" dd\"日\"", 58: "mm-dd", }, "th-th": { 59: "t0", 60: "t0.00", 61: "t#,##0", 62: "t#,##0.00", 67: "t0%", 68: "t0.00%", 69: "t# ?/?", 70: "t# ??/??", 71: "\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B", 72: "\u0E27-\u0E14\u0E14\u0E14-\u0E1B\u0E1B", 73: "\u0E27-\u0E14\u0E14\u0E14", 74: "\u0E14\u0E14\u0E14-\u0E1B\u0E1B", 75: "\u0E0A:\u0E19\u0E19", 76: "\u0E0A:\u0E19\u0E19:\u0E17\u0E17", 77: "\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B \u0E0A:\u0E19\u0E19", 78: "\u0E19\u0E19:\u0E17\u0E17", 79: "[\u0E0A]:\u0E19\u0E19:\u0E17\u0E17", 80: "\u0E19\u0E19:\u0E17\u0E17.0", 81: "d/m/bb", }, } // currencyNumFmt defined the currency number format map. currencyNumFmt = map[int]string{ 164: "\"¥\"#,##0.00", 165: "[$$-409]#,##0.00", 166: "[$$-45C]#,##0.00", 167: "[$$-1004]#,##0.00", 168: "[$$-404]#,##0.00", 169: "[$$-C09]#,##0.00", 170: "[$$-2809]#,##0.00", 171: "[$$-1009]#,##0.00", 172: "[$$-2009]#,##0.00", 173: "[$$-1409]#,##0.00", 174: "[$$-4809]#,##0.00", 175: "[$$-2C09]#,##0.00", 176: "[$$-2409]#,##0.00", 177: "[$$-1000]#,##0.00", 178: "#,##0.00\\ [$$-C0C]", 179: "[$$-475]#,##0.00", 180: "[$$-83E]#,##0.00", 181: "[$$-86B]\\ #,##0.00", 182: "[$$-340A]\\ #,##0.00", 183: "[$$-240A]#,##0.00", 184: "[$$-300A]\\ #,##0.00", 185: "[$$-440A]#,##0.00", 186: "[$$-80A]#,##0.00", 187: "[$$-500A]#,##0.00", 188: "[$$-540A]#,##0.00", 189: "[$$-380A]\\ #,##0.00", 190: "[$£-809]#,##0.00", 191: "[$£-491]#,##0.00", 192: "[$£-452]#,##0.00", 193: "[$¥-804]#,##0.00", 194: "[$¥-411]#,##0.00", 195: "[$¥-478]#,##0.00", 196: "[$¥-451]#,##0.00", 197: "[$¥-480]#,##0.00", 198: "#,##0.00\\ [$\u058F-42B]", 199: "[$\u060B-463]#,##0.00", 200: "[$\u060B-48C]#,##0.00", 201: "[$\u09F3-845]\\ #,##0.00", 202: "#,##0.00[$\u17DB-453]", 203: "[$\u20A1-140A]#,##0.00", 204: "[$\u20A6-468]\\ #,##0.00", 205: "[$\u20A6-470]\\ #,##0.00", 206: "[$\u20A9-412]#,##0.00", 207: "[$\u20AA-40D]\\ #,##0.00", 208: "#,##0.00\\ [$\u20AB-42A]", 209: "#,##0.00\\ [$\u20AC-42D]", 210: "#,##0.00\\ [$\u20AC-47E]", 211: "#,##0.00\\ [$\u20AC-403]", 212: "#,##0.00\\ [$\u20AC-483]", 213: "[$\u20AC-813]\\ #,##0.00", 214: "[$\u20AC-413]\\ #,##0.00", 215: "[$\u20AC-1809]#,##0.00", 216: "#,##0.00\\ [$\u20AC-425]", 217: "[$\u20AC-2]\\ #,##0.00", 218: "#,##0.00\\ [$\u20AC-1]", 219: "#,##0.00\\ [$\u20AC-40B]", 220: "#,##0.00\\ [$\u20AC-80C]", 221: "#,##0.00\\ [$\u20AC-40C]", 222: "#,##0.00\\ [$\u20AC-140C]", 223: "#,##0.00\\ [$\u20AC-180C]", 224: "[$\u20AC-200C]#,##0.00", 225: "#,##0.00\\ [$\u20AC-456]", 226: "#,##0.00\\ [$\u20AC-C07]", 227: "#,##0.00\\ [$\u20AC-407]", 228: "#,##0.00\\ [$\u20AC-1007]", 229: "#,##0.00\\ [$\u20AC-408]", 230: "#,##0.00\\ [$\u20AC-243B]", 231: "[$\u20AC-83C]#,##0.00", 232: "[$\u20AC-410]\\ #,##0.00", 233: "[$\u20AC-476]#,##0.00", 234: "#,##0.00\\ [$\u20AC-2C1A]", 235: "[$\u20AC-426]\\ #,##0.00", 236: "#,##0.00\\ [$\u20AC-427]", 237: "#,##0.00\\ [$\u20AC-82E]", 238: "#,##0.00\\ [$\u20AC-46E]", 239: "[$\u20AC-43A]#,##0.00", 240: "#,##0.00\\ [$\u20AC-C3B]", 241: "#,##0.00\\ [$\u20AC-482]", 242: "#,##0.00\\ [$\u20AC-816]", 243: "#,##0.00\\ [$\u20AC-301A]", 244: "#,##0.00\\ [$\u20AC-203B]", 245: "#,##0.00\\ [$\u20AC-41B]", 246: "#,##0.00\\ [$\u20AC-424]", 247: "#,##0.00\\ [$\u20AC-C0A]", 248: "#,##0.00\\ [$\u20AC-81D]", 249: "#,##0.00\\ [$\u20AC-484]", 250: "#,##0.00\\ [$\u20AC-42E]", 251: "[$\u20AC-462]\\ #,##0.00", 252: "#,##0.00\\ [$₭-454]", 253: "#,##0.00\\ [$₮-450]", 254: "[$\u20AE-C50]#,##0.00", 255: "[$\u20B1-3409]#,##0.00", 256: "[$\u20B1-464]#,##0.00", 257: "#,##0.00[$\u20B4-422]", 258: "[$\u20B8-43F]#,##0.00", 259: "[$\u20B9-460]#,##0.00", 260: "[$\u20B9-4009]\\ #,##0.00", 261: "[$\u20B9-447]\\ #,##0.00", 262: "[$\u20B9-439]\\ #,##0.00", 263: "[$\u20B9-44B]\\ #,##0.00", 264: "[$\u20B9-860]#,##0.00", 265: "[$\u20B9-457]\\ #,##0.00", 266: "[$\u20B9-458]#,##0.00", 267: "[$\u20B9-44E]\\ #,##0.00", 268: "[$\u20B9-861]#,##0.00", 269: "[$\u20B9-448]\\ #,##0.00", 270: "[$\u20B9-446]\\ #,##0.00", 271: "[$\u20B9-44F]\\ #,##0.00", 272: "[$\u20B9-459]#,##0.00", 273: "[$\u20B9-449]\\ #,##0.00", 274: "[$\u20B9-820]#,##0.00", 275: "#,##0.00\\ [$\u20BA-41F]", 276: "#,##0.00\\ [$\u20BC-42C]", 277: "#,##0.00\\ [$\u20BC-82C]", 278: "#,##0.00\\ [$\u20BD-419]", 279: "#,##0.00[$\u20BD-485]", 280: "#,##0.00\\ [$\u20BE-437]", 281: "[$B/.-180A]\\ #,##0.00", 282: "[$Br-472]#,##0.00", 283: "[$Br-477]#,##0.00", 284: "#,##0.00[$Br-473]", 285: "[$Bs-46B]\\ #,##0.00", 286: "[$Bs-400A]\\ #,##0.00", 287: "[$Bs.-200A]\\ #,##0.00", 288: "[$BWP-832]\\ #,##0.00", 289: "[$C$-4C0A]#,##0.00", 290: "[$CA$-85D]#,##0.00", 291: "[$CA$-47C]#,##0.00", 292: "[$CA$-45D]#,##0.00", 293: "[$CFA-340C]#,##0.00", 294: "[$CFA-280C]#,##0.00", 295: "#,##0.00\\ [$CFA-867]", 296: "#,##0.00\\ [$CFA-488]", 297: "#,##0.00\\ [$CHF-100C]", 298: "[$CHF-1407]\\ #,##0.00", 299: "[$CHF-807]\\ #,##0.00", 300: "[$CHF-810]\\ #,##0.00", 301: "[$CHF-417]\\ #,##0.00", 302: "[$CLP-47A]\\ #,##0.00", 303: "[$CN¥-850]#,##0.00", 304: "#,##0.00\\ [$DZD-85F]", 305: "[$FCFA-2C0C]#,##0.00", 306: "#,##0.00\\ [$Ft-40E]", 307: "[$G-3C0C]#,##0.00", 308: "[$Gs.-3C0A]\\ #,##0.00", 309: "[$GTQ-486]#,##0.00", 310: "[$HK$-C04]#,##0.00", 311: "[$HK$-3C09]#,##0.00", 312: "#,##0.00\\ [$HRK-41A]", 313: "[$IDR-3809]#,##0.00", 314: "[$IQD-492]#,##0.00", 315: "#,##0.00\\ [$ISK-40F]", 316: "[$K-455]#,##0.00", 317: "#,##0.00\\ [$K\u010D-405]", 318: "#,##0.00\\ [$KM-141A]", 319: "#,##0.00\\ [$KM-101A]", 320: "#,##0.00\\ [$KM-181A]", 321: "[$kr-438]\\ #,##0.00", 322: "[$kr-43B]\\ #,##0.00", 323: "#,##0.00\\ [$kr-83B]", 324: "[$kr-414]\\ #,##0.00", 325: "[$kr-814]\\ #,##0.00", 326: "#,##0.00\\ [$kr-41D]", 327: "[$kr.-406]\\ #,##0.00", 328: "[$kr.-46F]\\ #,##0.00", 329: "[$Ksh-441]#,##0.00", 330: "[$L-818]#,##0.00", 331: "[$L-819]#,##0.00", 332: "[$L-480A]\\ #,##0.00", 333: "#,##0.00\\ [$Lek\u00EB-41C]", 334: "[$MAD-45F]#,##0.00", 335: "[$MAD-380C]#,##0.00", 336: "#,##0.00\\ [$MAD-105F]", 337: "[$MOP$-1404]#,##0.00", 338: "#,##0.00\\ [$MVR-465]_-", 339: "#,##0.00[$Nfk-873]", 340: "[$NGN-466]#,##0.00", 341: "[$NGN-467]#,##0.00", 342: "[$NGN-469]#,##0.00", 343: "[$NGN-471]#,##0.00", 344: "[$NOK-103B]\\ #,##0.00", 345: "[$NOK-183B]\\ #,##0.00", 346: "[$NZ$-481]#,##0.00", 347: "[$PKR-859]\\ #,##0.00", 348: "[$PYG-474]#,##0.00", 349: "[$Q-100A]#,##0.00", 350: "[$R-436]\\ #,##0.00", 351: "[$R-1C09]\\ #,##0.00", 352: "[$R-435]\\ #,##0.00", 353: "[$R$-416]\\ #,##0.00", 354: "[$RD$-1C0A]#,##0.00", 355: "#,##0.00\\ [$RF-487]", 356: "[$RM-4409]#,##0.00", 357: "[$RM-43E]#,##0.00", 358: "#,##0.00\\ [$RON-418]", 359: "[$Rp-421]#,##0.00", 360: "[$Rs-420]#,##0.00_-", 361: "[$Rs.-849]\\ #,##0.00", 362: "#,##0.00\\ [$RSD-81A]", 363: "#,##0.00\\ [$RSD-C1A]", 364: "#,##0.00\\ [$RUB-46D]", 365: "#,##0.00\\ [$RUB-444]", 366: "[$S/.-C6B]\\ #,##0.00", 367: "[$S/.-280A]\\ #,##0.00", 368: "#,##0.00\\ [$SEK-143B]", 369: "#,##0.00\\ [$SEK-1C3B]", 370: "#,##0.00\\ [$so\u02BBm-443]", 371: "#,##0.00\\ [$so\u02BBm-843]", 372: "#,##0.00\\ [$SYP-45A]", 373: "[$THB-41E]#,##0.00", 374: "#,##0.00[$TMT-442]", 375: "[$US$-3009]#,##0.00", 376: "[$ZAR-46C]\\ #,##0.00", 377: "[$ZAR-430]#,##0.00", 378: "[$ZAR-431]#,##0.00", 379: "[$ZAR-432]\\ #,##0.00", 380: "[$ZAR-433]#,##0.00", 381: "[$ZAR-434]\\ #,##0.00", 382: "#,##0.00\\ [$z\u0142-415]", 383: "#,##0.00\\ [$\u0434\u0435\u043D-42F]", 384: "#,##0.00\\ [$КМ-201A]", 385: "#,##0.00\\ [$КМ-1C1A]", 386: "#,##0.00\\ [$\u043B\u0432.-402]", 387: "#,##0.00\\ [$р.-423]", 388: "#,##0.00\\ [$\u0441\u043E\u043C-440]", 389: "#,##0.00\\ [$\u0441\u043E\u043C-428]", 390: "[$\u062C.\u0645.-C01]\\ #,##0.00_-", 391: "[$\u062F.\u0623.-2C01]\\ #,##0.00_-", 392: "[$\u062F.\u0625.-3801]\\ #,##0.00_-", 393: "[$\u062F.\u0628.-3C01]\\ #,##0.00_-", 394: "[$\u062F.\u062A.-1C01]\\ #,##0.00_-", 395: "[$\u062F.\u062C.-1401]\\ #,##0.00_-", 396: "[$\u062F.\u0639.-801]\\ #,##0.00_-", 397: "[$\u062F.\u0643.-3401]\\ #,##0.00_-", 398: "[$\u062F.\u0644.-1001]#,##0.00_-", 399: "[$\u062F.\u0645.-1801]\\ #,##0.00_-", 400: "[$\u0631-846]\\ #,##0.00", 401: "[$\u0631.\u0633.-401]\\ #,##0.00_-", 402: "[$\u0631.\u0639.-2001]\\ #,##0.00_-", 403: "[$\u0631.\u0642.-4001]\\ #,##0.00_-", 404: "[$\u0631.\u064A.-2401]\\ #,##0.00_-", 405: "[$\u0631\u06CC\u0627\u0644-429]#,##0.00_-", 406: "[$\u0644.\u0633.-2801]\\ #,##0.00_-", 407: "[$\u0644.\u0644.-3001]\\ #,##0.00_-", 408: "[$\u1265\u122D-45E]#,##0.00", 409: "[$\u0930\u0942-461]#,##0.00", 410: "[$\u0DBB\u0DD4.-45B]\\ #,##0.00", 411: "[$ADP]\\ #,##0.00", 412: "[$AED]\\ #,##0.00", 413: "[$AFA]\\ #,##0.00", 414: "[$AFN]\\ #,##0.00", 415: "[$ALL]\\ #,##0.00", 416: "[$AMD]\\ #,##0.00", 417: "[$ANG]\\ #,##0.00", 418: "[$AOA]\\ #,##0.00", 419: "[$ARS]\\ #,##0.00", 420: "[$ATS]\\ #,##0.00", 421: "[$AUD]\\ #,##0.00", 422: "[$AWG]\\ #,##0.00", 423: "[$AZM]\\ #,##0.00", 424: "[$AZN]\\ #,##0.00", 425: "[$BAM]\\ #,##0.00", 426: "[$BBD]\\ #,##0.00", 427: "[$BDT]\\ #,##0.00", 428: "[$BEF]\\ #,##0.00", 429: "[$BGL]\\ #,##0.00", 430: "[$BGN]\\ #,##0.00", 431: "[$BHD]\\ #,##0.00", 432: "[$BIF]\\ #,##0.00", 433: "[$BMD]\\ #,##0.00", 434: "[$BND]\\ #,##0.00", 435: "[$BOB]\\ #,##0.00", 436: "[$BOV]\\ #,##0.00", 437: "[$BRL]\\ #,##0.00", 438: "[$BSD]\\ #,##0.00", 439: "[$BTN]\\ #,##0.00", 440: "[$BWP]\\ #,##0.00", 441: "[$BYR]\\ #,##0.00", 442: "[$BZD]\\ #,##0.00", 443: "[$CAD]\\ #,##0.00", 444: "[$CDF]\\ #,##0.00", 445: "[$CHE]\\ #,##0.00", 446: "[$CHF]\\ #,##0.00", 447: "[$CHW]\\ #,##0.00", 448: "[$CLF]\\ #,##0.00", 449: "[$CLP]\\ #,##0.00", 450: "[$CNY]\\ #,##0.00", 451: "[$COP]\\ #,##0.00", 452: "[$COU]\\ #,##0.00", 453: "[$CRC]\\ #,##0.00", 454: "[$CSD]\\ #,##0.00", 455: "[$CUC]\\ #,##0.00", 456: "[$CVE]\\ #,##0.00", 457: "[$CYP]\\ #,##0.00", 458: "[$CZK]\\ #,##0.00", 459: "[$DEM]\\ #,##0.00", 460: "[$DJF]\\ #,##0.00", 461: "[$DKK]\\ #,##0.00", 462: "[$DOP]\\ #,##0.00", 463: "[$DZD]\\ #,##0.00", 464: "[$ECS]\\ #,##0.00", 465: "[$ECV]\\ #,##0.00", 466: "[$EEK]\\ #,##0.00", 467: "[$EGP]\\ #,##0.00", 468: "[$ERN]\\ #,##0.00", 469: "[$ESP]\\ #,##0.00", 470: "[$ETB]\\ #,##0.00", 471: "[$EUR]\\ #,##0.00", 472: "[$FIM]\\ #,##0.00", 473: "[$FJD]\\ #,##0.00", 474: "[$FKP]\\ #,##0.00", 475: "[$FRF]\\ #,##0.00", 476: "[$GBP]\\ #,##0.00", 477: "[$GEL]\\ #,##0.00", 478: "[$GHC]\\ #,##0.00", 479: "[$GHS]\\ #,##0.00", 480: "[$GIP]\\ #,##0.00", 481: "[$GMD]\\ #,##0.00", 482: "[$GNF]\\ #,##0.00", 483: "[$GRD]\\ #,##0.00", 484: "[$GTQ]\\ #,##0.00", 485: "[$GYD]\\ #,##0.00", 486: "[$HKD]\\ #,##0.00", 487: "[$HNL]\\ #,##0.00", 488: "[$HRK]\\ #,##0.00", 489: "[$HTG]\\ #,##0.00", 490: "[$HUF]\\ #,##0.00", 491: "[$IDR]\\ #,##0.00", 492: "[$IEP]\\ #,##0.00", 493: "[$ILS]\\ #,##0.00", 494: "[$INR]\\ #,##0.00", 495: "[$IQD]\\ #,##0.00", 496: "[$IRR]\\ #,##0.00", 497: "[$ISK]\\ #,##0.00", 498: "[$ITL]\\ #,##0.00", 499: "[$JMD]\\ #,##0.00", 500: "[$JOD]\\ #,##0.00", 501: "[$JPY]\\ #,##0.00", 502: "[$KAF]\\ #,##0.00", 503: "[$KES]\\ #,##0.00", 504: "[$KGS]\\ #,##0.00", 505: "[$KHR]\\ #,##0.00", 506: "[$KMF]\\ #,##0.00", 507: "[$KPW]\\ #,##0.00", 508: "[$KRW]\\ #,##0.00", 509: "[$KWD]\\ #,##0.00", 510: "[$KYD]\\ #,##0.00", 511: "[$KZT]\\ #,##0.00", 512: "[$LAK]\\ #,##0.00", 513: "[$LBP]\\ #,##0.00", 514: "[$LKR]\\ #,##0.00", 515: "[$LRD]\\ #,##0.00", 516: "[$LSL]\\ #,##0.00", 517: "[$LTL]\\ #,##0.00", 518: "[$LUF]\\ #,##0.00", 519: "[$LVL]\\ #,##0.00", 520: "[$LYD]\\ #,##0.00", 521: "[$MAD]\\ #,##0.00", 522: "[$MDL]\\ #,##0.00", 523: "[$MGA]\\ #,##0.00", 524: "[$MGF]\\ #,##0.00", 525: "[$MKD]\\ #,##0.00", 526: "[$MMK]\\ #,##0.00", 527: "[$MNT]\\ #,##0.00", 528: "[$MOP]\\ #,##0.00", 529: "[$MRO]\\ #,##0.00", 530: "[$MTL]\\ #,##0.00", 531: "[$MUR]\\ #,##0.00", 532: "[$MVR]\\ #,##0.00", 533: "[$MWK]\\ #,##0.00", 534: "[$MXN]\\ #,##0.00", 535: "[$MXV]\\ #,##0.00", 536: "[$MYR]\\ #,##0.00", 537: "[$MZM]\\ #,##0.00", 538: "[$MZN]\\ #,##0.00", 539: "[$NAD]\\ #,##0.00", 540: "[$NGN]\\ #,##0.00", 541: "[$NIO]\\ #,##0.00", 542: "[$NLG]\\ #,##0.00", 543: "[$NOK]\\ #,##0.00", 544: "[$NPR]\\ #,##0.00", 545: "[$NTD]\\ #,##0.00", 546: "[$NZD]\\ #,##0.00", 547: "[$OMR]\\ #,##0.00", 548: "[$PAB]\\ #,##0.00", 549: "[$PEN]\\ #,##0.00", 550: "[$PGK]\\ #,##0.00", 551: "[$PHP]\\ #,##0.00", 552: "[$PKR]\\ #,##0.00", 553: "[$PLN]\\ #,##0.00", 554: "[$PTE]\\ #,##0.00", 555: "[$PYG]\\ #,##0.00", 556: "[$QAR]\\ #,##0.00", 557: "[$ROL]\\ #,##0.00", 558: "[$RON]\\ #,##0.00", 559: "[$RSD]\\ #,##0.00", 560: "[$RUB]\\ #,##0.00", 561: "[$RUR]\\ #,##0.00", 562: "[$RWF]\\ #,##0.00", 563: "[$SAR]\\ #,##0.00", 564: "[$SBD]\\ #,##0.00", 565: "[$SCR]\\ #,##0.00", 566: "[$SDD]\\ #,##0.00", 567: "[$SDG]\\ #,##0.00", 568: "[$SDP]\\ #,##0.00", 569: "[$SEK]\\ #,##0.00", 570: "[$SGD]\\ #,##0.00", 571: "[$SHP]\\ #,##0.00", 572: "[$SIT]\\ #,##0.00", 573: "[$SKK]\\ #,##0.00", 574: "[$SLL]\\ #,##0.00", 575: "[$SOS]\\ #,##0.00", 576: "[$SPL]\\ #,##0.00", 577: "[$SRD]\\ #,##0.00", 578: "[$SRG]\\ #,##0.00", 579: "[$STD]\\ #,##0.00", 580: "[$SVC]\\ #,##0.00", 581: "[$SYP]\\ #,##0.00", 582: "[$SZL]\\ #,##0.00", 583: "[$THB]\\ #,##0.00", 584: "[$TJR]\\ #,##0.00", 585: "[$TJS]\\ #,##0.00", 586: "[$TMM]\\ #,##0.00", 587: "[$TMT]\\ #,##0.00", 588: "[$TND]\\ #,##0.00", 589: "[$TOP]\\ #,##0.00", 590: "[$TRL]\\ #,##0.00", 591: "[$TRY]\\ #,##0.00", 592: "[$TTD]\\ #,##0.00", 593: "[$TWD]\\ #,##0.00", 594: "[$TZS]\\ #,##0.00", 595: "[$UAH]\\ #,##0.00", 596: "[$UGX]\\ #,##0.00", 597: "[$USD]\\ #,##0.00", 598: "[$USN]\\ #,##0.00", 599: "[$USS]\\ #,##0.00", 600: "[$UYI]\\ #,##0.00", 601: "[$UYU]\\ #,##0.00", 602: "[$UZS]\\ #,##0.00", 603: "[$VEB]\\ #,##0.00", 604: "[$VEF]\\ #,##0.00", 605: "[$VND]\\ #,##0.00", 606: "[$VUV]\\ #,##0.00", 607: "[$WST]\\ #,##0.00", 608: "[$XAF]\\ #,##0.00", 609: "[$XAG]\\ #,##0.00", 610: "[$XAU]\\ #,##0.00", 611: "[$XB5]\\ #,##0.00", 612: "[$XBA]\\ #,##0.00", 613: "[$XBB]\\ #,##0.00", 614: "[$XBC]\\ #,##0.00", 615: "[$XBD]\\ #,##0.00", 616: "[$XCD]\\ #,##0.00", 617: "[$XDR]\\ #,##0.00", 618: "[$XFO]\\ #,##0.00", 619: "[$XFU]\\ #,##0.00", 620: "[$XOF]\\ #,##0.00", 621: "[$XPD]\\ #,##0.00", 622: "[$XPF]\\ #,##0.00", 623: "[$XPT]\\ #,##0.00", 624: "[$XTS]\\ #,##0.00", 625: "[$XXX]\\ #,##0.00", 626: "[$YER]\\ #,##0.00", 627: "[$YUM]\\ #,##0.00", 628: "[$ZAR]\\ #,##0.00", 629: "[$ZMK]\\ #,##0.00", 630: "[$ZMW]\\ #,##0.00", 631: "[$ZWD]\\ #,##0.00", 632: "[$ZWL]\\ #,##0.00", 633: "[$ZWN]\\ #,##0.00", 634: "[$ZWR]\\ #,##0.00", } // supportedTokenTypes list the supported number format token types // currently. supportedTokenTypes = []string{ nfp.TokenTypeAlignment, nfp.TokenSubTypeCurrencyString, nfp.TokenSubTypeLanguageInfo, nfp.TokenTypeColor, nfp.TokenTypeCurrencyLanguage, nfp.TokenTypeDateTimes, nfp.TokenTypeDecimalPoint, nfp.TokenTypeDenominator, nfp.TokenTypeDigitalPlaceHolder, nfp.TokenTypeElapsedDateTimes, nfp.TokenTypeExponential, nfp.TokenTypeFraction, nfp.TokenTypeGeneral, nfp.TokenTypeHashPlaceHolder, nfp.TokenTypeLiteral, nfp.TokenTypePercent, nfp.TokenTypeRepeatsChar, nfp.TokenTypeSwitchArgument, nfp.TokenTypeTextPlaceHolder, nfp.TokenTypeThousandsSeparator, nfp.TokenTypeZeroPlaceHolder, } // supportedNumberTokenTypes list the supported number token types. supportedNumberTokenTypes = []string{ nfp.TokenTypeDenominator, nfp.TokenTypeDigitalPlaceHolder, nfp.TokenTypeExponential, nfp.TokenTypeFraction, nfp.TokenTypeHashPlaceHolder, nfp.TokenTypePercent, nfp.TokenTypeZeroPlaceHolder, } // supportedDateTimeTokenTypes list the supported date and time token types. supportedDateTimeTokenTypes = []string{ nfp.TokenTypeDateTimes, nfp.TokenTypeElapsedDateTimes, } // supportedLanguageInfo directly maps the supported language decimal ID and // tags. supportedLanguageInfo = map[int]languageInfo{ 54: {tags: []string{"af"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans, weekdayNames: weekdayNamesAfrikaans, weekdayNamesAbbr: weekdayNamesAfrikaansAbbr}, 1078: {tags: []string{"af-ZA"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans, weekdayNames: weekdayNamesAfrikaans, weekdayNamesAbbr: weekdayNamesAfrikaansAbbr}, 28: {tags: []string{"sq"}, localMonth: localMonthsNameAlbanian, apFmt: apFmtAlbanian, weekdayNames: weekdayNamesAlbanian, weekdayNamesAbbr: weekdayNamesAlbanianAbbr}, 1052: {tags: []string{"sq-AL"}, localMonth: localMonthsNameAlbanian, apFmt: apFmtAlbanian, weekdayNames: weekdayNamesAlbanian, weekdayNamesAbbr: weekdayNamesAlbanianAbbr}, 132: {tags: []string{"gsw"}, localMonth: localMonthsNameAlsatian, apFmt: apFmtAlsatian, weekdayNames: weekdayNamesAlsatian, weekdayNamesAbbr: weekdayNamesAlsatianAbbr}, 1156: {tags: []string{"gsw-FR"}, localMonth: localMonthsNameAlsatianFrance, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAlsatianFrance, weekdayNamesAbbr: weekdayNamesAlsatianFranceAbbr}, 94: {tags: []string{"am"}, localMonth: localMonthsNameAmharic, apFmt: apFmtAmharic, weekdayNames: weekdayNamesAmharic, weekdayNamesAbbr: weekdayNamesAmharicAbbr}, 1118: {tags: []string{"am-ET"}, localMonth: localMonthsNameAmharic, apFmt: apFmtAmharic, weekdayNames: weekdayNamesAmharic, weekdayNamesAbbr: weekdayNamesAmharicAbbr}, 1: {tags: []string{"ar"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 5121: {tags: []string{"ar-DZ"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 15361: {tags: []string{"ar-BH"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 3073: {tags: []string{"ar-EG"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 2049: {tags: []string{"ar-IQ"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 11265: {tags: []string{"ar-JO"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 13313: {tags: []string{"ar-KW"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 12289: {tags: []string{"ar-LB"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 4097: {tags: []string{"ar-LY"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 6145: {tags: []string{"ar-MA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 8193: {tags: []string{"ar-OM"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 16385: {tags: []string{"ar-QA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 1025: {tags: []string{"ar-SA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 10241: {tags: []string{"ar-SY"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 7169: {tags: []string{"ar-TN"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 14337: {tags: []string{"ar-AE"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 9217: {tags: []string{"ar-YE"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 43: {tags: []string{"hy"}, localMonth: localMonthsNameArmenian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesArmenian, weekdayNamesAbbr: weekdayNamesArmenianAbbr}, 1067: {tags: []string{"hy-AM"}, localMonth: localMonthsNameArmenian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesArmenian, weekdayNamesAbbr: weekdayNamesArmenianAbbr}, 77: {tags: []string{"as"}, localMonth: localMonthsNameAssamese, apFmt: apFmtAssamese, weekdayNames: weekdayNamesAssamese, weekdayNamesAbbr: weekdayNamesAssameseAbbr}, 1101: {tags: []string{"as-IN"}, localMonth: localMonthsNameAssamese, apFmt: apFmtAssamese, weekdayNames: weekdayNamesAssamese, weekdayNamesAbbr: weekdayNamesAssameseAbbr}, 29740: {tags: []string{"az-Cyrl"}, localMonth: localMonthsNameAzerbaijaniCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijaniCyrillic, weekdayNamesAbbr: weekdayNamesAzerbaijaniCyrillicAbbr}, 2092: {tags: []string{"az-Cyrl-AZ"}, localMonth: localMonthsNameAzerbaijaniCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijaniCyrillic, weekdayNamesAbbr: weekdayNamesAzerbaijaniCyrillicAbbr}, 44: {tags: []string{"az"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr}, 30764: {tags: []string{"az-Latn"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr}, 1068: {tags: []string{"az-Latn-AZ"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr}, 69: {tags: []string{"bn"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr}, 2117: {tags: []string{"bn-BD"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr}, 1093: {tags: []string{"bn-IN"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr}, 109: {tags: []string{"ba"}, localMonth: localMonthsNameBashkir, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBashkir, weekdayNamesAbbr: weekdayNamesBashkirAbbr}, 1133: {tags: []string{"ba-RU"}, localMonth: localMonthsNameBashkir, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBashkir, weekdayNamesAbbr: weekdayNamesBashkirAbbr}, 45: {tags: []string{"eu"}, localMonth: localMonthsNameBasque, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBasque, weekdayNamesAbbr: weekdayNamesBasqueAbbr}, 1069: {tags: []string{"eu-ES"}, localMonth: localMonthsNameBasque, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBasque, weekdayNamesAbbr: weekdayNamesBasqueAbbr}, 35: {tags: []string{"be"}, localMonth: localMonthsNameBelarusian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBelarusian, weekdayNamesAbbr: weekdayNamesBelarusianAbbr}, 1059: {tags: []string{"be-BY"}, localMonth: localMonthsNameBelarusian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBelarusian, weekdayNamesAbbr: weekdayNamesBelarusianAbbr}, 25626: {tags: []string{"bs-Cyrl"}, localMonth: localMonthsNameBosnianCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnianCyrillic, weekdayNamesAbbr: weekdayNamesBosnianCyrillicAbbr}, 8218: {tags: []string{"bs-Cyrl-BA"}, localMonth: localMonthsNameBosnianCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnianCyrillic, weekdayNamesAbbr: weekdayNamesBosnianCyrillicAbbr}, 26650: {tags: []string{"bs-Latn"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr}, 30746: {tags: []string{"bs"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr}, 5146: {tags: []string{"bs-Latn-BA"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr}, 126: {tags: []string{"br"}, localMonth: localMonthsNameBreton, apFmt: apFmtBreton, weekdayNames: weekdayNamesBreton, weekdayNamesAbbr: weekdayNamesBretonAbbr}, 1150: {tags: []string{"br-FR"}, localMonth: localMonthsNameBreton, apFmt: apFmtBreton, weekdayNames: weekdayNamesBreton, weekdayNamesAbbr: weekdayNamesBretonAbbr}, 2: {tags: []string{"bg"}, localMonth: localMonthsNameBulgarian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBulgarian, weekdayNamesAbbr: weekdayNamesBulgarianAbbr}, 1026: {tags: []string{"bg-BG"}, localMonth: localMonthsNameBulgarian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBulgarian, weekdayNamesAbbr: weekdayNamesBulgarianAbbr}, 85: {tags: []string{"my"}, localMonth: localMonthsNameBurmese, apFmt: apFmtBurmese, weekdayNames: weekdayNamesBurmese, weekdayNamesAbbr: weekdayNamesBurmese}, 1109: {tags: []string{"my-MM"}, localMonth: localMonthsNameBurmese, apFmt: apFmtBurmese, weekdayNames: weekdayNamesBurmese, weekdayNamesAbbr: weekdayNamesBurmese}, 3: {tags: []string{"ca"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr}, 1027: {tags: []string{"ca-ES"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr}, 1119: {tags: []string{"tzm-Arab-MA"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr}, 146: {tags: []string{"ku"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish}, 31890: {tags: []string{"ku-Arab"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish}, 1170: {tags: []string{"ku-Arab-IQ"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish}, 92: {tags: []string{"chr"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr}, 31836: {tags: []string{"chr-Cher"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr}, 1116: {tags: []string{"chr-Cher-US"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr}, 4: {tags: []string{"zh-Hans"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2}, 30724: {tags: []string{"zh"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2}, 2052: {tags: []string{"zh-CN"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr}, 4100: {tags: []string{"zh-SG"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr}, 31748: {tags: []string{"zh-Hant"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2}, 3076: {tags: []string{"zh-HK"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2}, 5124: {tags: []string{"zh-MO"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2}, 1028: {tags: []string{"zh-TW"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2, useGannen: true}, 131: {tags: []string{"co"}, localMonth: localMonthsNameCorsican, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCorsican, weekdayNamesAbbr: weekdayNamesCorsicanAbbr}, 1155: {tags: []string{"co-FR"}, localMonth: localMonthsNameCorsican, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCorsican, weekdayNamesAbbr: weekdayNamesCorsicanAbbr}, 26: {tags: []string{"hr"}, localMonth: localMonthsNameCroatian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr}, 1050: {tags: []string{"hr-HR"}, localMonth: localMonthsNameCroatian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr}, 4122: {tags: []string{"hr-BA"}, localMonth: localMonthsNameCroatianLatin, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr}, 5: {tags: []string{"cs"}, localMonth: localMonthsNameCzech, apFmt: apFmtCzech, weekdayNames: weekdayNamesCzech, weekdayNamesAbbr: weekdayNamesCzechAbbr}, 1029: {tags: []string{"cs-CZ"}, localMonth: localMonthsNameCzech, apFmt: apFmtCzech, weekdayNames: weekdayNamesCzech, weekdayNamesAbbr: weekdayNamesCzechAbbr}, 6: {tags: []string{"da"}, localMonth: localMonthsNameDanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDanish, weekdayNamesAbbr: weekdayNamesDanishAbbr}, 1030: {tags: []string{"da-DK"}, localMonth: localMonthsNameDanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDanish, weekdayNamesAbbr: weekdayNamesDanishAbbr}, 140: {tags: []string{"prs"}, localMonth: localMonthsNameDari, apFmt: apFmtPersian, weekdayNames: weekdayNamesDari, weekdayNamesAbbr: weekdayNamesDari}, 1164: {tags: []string{"prs-AF"}, localMonth: localMonthsNameDariAfghanistan, apFmt: apFmtDari, weekdayNames: weekdayNamesDari, weekdayNamesAbbr: weekdayNamesDari}, 101: {tags: []string{"dv"}, localMonth: localMonthsNameDivehi, apFmt: apFmtDivehi, weekdayNames: weekdayNamesDivehi, weekdayNamesAbbr: weekdayNamesDivehi}, 1125: {tags: []string{"dv-MV"}, localMonth: localMonthsNameDivehi, apFmt: apFmtDivehi, weekdayNames: weekdayNamesDivehi, weekdayNamesAbbr: weekdayNamesDivehi}, 19: {tags: []string{"nl"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr}, 2067: {tags: []string{"nl-BE"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr}, 1043: {tags: []string{"nl-NL"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr}, 3153: {tags: []string{"dz-BT"}, localMonth: localMonthsNameDzongkha, apFmt: apFmtDzongkha, weekdayNames: weekdayNamesDzongkha, weekdayNamesAbbr: weekdayNamesDzongkhaAbbr}, 9: {tags: []string{"en"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 4096: {tags: []string{ "aa", "aa-DJ", "aa-ER", "aa-ET", "af-NA", "agq", "agq-CM", "ak", "ak-GH", "sq-MK", "gsw-LI", "gsw-CH", "ar-TD", "ar-KM", "ar-DJ", "ar-ER", "ar-IL", "ar-MR", "ar-PS", "ar-SO", "ar-SS", "ar-SD", "ar-001", "ast", "ast-ES", "asa", "asa-TZ", "ksf", "ksf-CM", "bm", "bm-Latn-ML", "bas", "bas-CM", "bem", "bem-ZM", "bez", "bez-TZ", "bho", "bho-Deva", "bho-Deva-IN", "byn", "byn-ER", "brx", "brx-IN", "ca-AD", "ca-FR", "ca-IT", "ceb", "ceb-Latn", "ceb-Latn-PH", "tzm-Latn-MA", "ccp", "ccp-Cakm", "ccp-Cakm-BD", "ccp-Cakm-IN", "ce-RU", "cgg", "cgg-UG", "cu-RU", "cv", "cv-Cyrl", "cv-Cyrl-RU", "swc", "swc-CD", "kw", "kw-GB", "da-GL", "dua", "dua-CM", "nl-AW", "nl-BQ", "nl-CW", "nl-SX", "nl-SR", "dz", "ebu", "ebu-KE", "en-AS", "en-AI", "en-AG", "en-AT", "en-BS", "en-BB", "en-BE", "en-BM", "en-BW", "en-IO", "en-VG", "en-BI", "en-CM", "en-KY", "en-CX", "en-CC", "en-CK", "en-CY", "en-DK", "en-DM", "en-ER", "en-150", "en-FK", "en-FI", "en-FJ", "en-GM", "en-DE", "en-GH", "en-GI", "en-GD", "en-GU", "en-GG", "en-GY", "en-IM", "en-IL", "en-JE", "en-KE", "en-KI", "en-LS", "en-LR", "en-MO", "en-MG", "en-MW", "en-MV", "en-MT", "en-MH", "en-MU", "en-FM", "en-MS", "en-NA", "en-NR", "en-NL", "en-NG", "en-NU", "en-NF", "en-MP", "en-PK", "en-PW", "en-PG", "en-PN", "en-PR", "en-RW", "en-KN", "en-LC", "en-VC", "en-WS", "en-SC", "en-SL", "en-SX", "en-SI", "en-SB", "en-SS", "en-SH", "en-SD", "en-SZ", "en-SE", "en-CH", "en-TZ", "en-TK", "en-TO", "en-TC", "en-TV", "en-UG", "en-UM", "en-VI", "en-VU", "en-001", "en-ZM", "eo", "eo-001", "ee", "ee-GH", "ee-TG", "ewo", "ewo-CM", "fo-DK", "fr-DZ", "fr-BJ", "fr-BF", "fr-BI", "fr-CF", "fr-TD", "fr-KM", "fr-CG", "fr-DJ", "fr-GQ", "fr-GF", "fr-PF", "fr-GA", "fr-GP", "fr-GN", "fr-MG", "fr-MQ", "fr-MR", "fr-MU", "fr-YT", "fr-NC", "fr-NE", "fr-RW", "fr-BL", "fr-MF", "fr-PM", "fr-SC", "fr-SY", "fr-TG", "fr-TN", "fr-VU", "fr-WF", "fur", "fur-IT", "ff-Latn-BF", "ff-CM", "ff-Latn-CM", "ff-Latn-GM", "ff-Latn-GH", "ff-GN", "ff-Latn-GN", "ff-Latn-GW", "ff-Latn-LR", "ff-MR", "ff-Latn-MR", "ff-Latn-NE", "ff-Latn-SL", "lg", "lg-UG", "de-BE", "de-IT", "el-CY", "guz", "guz-KE", "bgc", "bgc-Deva", "bgc-Deva-IN", "ha-Latn-GH", "ha-Latn-NE", "hi-Latn", "hi-Latn-IN", "ia", "ia-FR", "ia-001", "it-SM", "it-VA", "jv", "jv-Latn", "jv-Latn-ID", "dyo", "dyo-SN", "kea", "kea-CV", "kab", "kab-DZ", "kgp", "kgp-Latn", "kgp-Latn-BR", "kkj", "kkj-CM", "kln", "kln-KE", "kam", "kam-KE", "ks-Arab-IN", "ki", "ki-KE", "sw-TZ", "sw-UG", "ko-KP", "khq", "khq-ML", "ses", "ses-ML", "nmg", "nmg-CM", "ku-Arab-IR", "lkt", "lkt-US", "lag", "lag-TZ", "ln", "ln-AO", "ln-CF", "ln-CG", "ln-CD", "nds", "nds-DE", "nds-NL", "lu", "lu-CD", "luo", "luo-KE", "luy", "luy-KE", "jmc", "jmc-TZ", "mgh", "mgh-MZ", "kde", "kde-TZ", "mg", "mg-MG", "gv", "gv-IM", "mas", "mas-KE", "mas-TZ", "mzn-IR", "mer", "mer-KE", "mgo", "mgo-CM", "mfe", "mfe-MU", "mua", "mua-CM", "nqo", "nqo-GN", "naq", "naq-NA", "nnh", "nnh-CM", "jgo", "jgo-CM", "yrl", "yrl-Latn", "yrl-Latn-BR", "yrl-Latn-CO", "yrl-Latn-VE", "lrc-IQ", "lrc-IR", "nd", "nd-ZW", "nb-SJ", "nus", "nus-SD", "nus-SS", "nyn", "nyn-UG", "oc-ES", "om-KE", "os", "os-GE", "os-RU", "ps-PK", "fa-AF", "pt-AO", "pt-CV", "pt-GQ", "pt-GW", "pt-LU", "pt-MO", "pt-MZ", "pt-ST", "pt-CH", "pt-TL", "prg-001", "raj", "raj-Deva", "raj-Deva-IN", "ksh", "ksh-DE", "rof", "rof-TZ", "rn", "rn-BI", "ru-BY", "ru-KZ", "ru-KG", "ru-UA", "rwk", "rwk-TZ", "ssy", "ssy-ER", "saq", "saq-KE", "sg", "sg-CF", "sbp", "sbp-TZ", "sc", "sc-Latn", "sc-Latn-IT", "seh", "seh-MZ", "ksb", "ksb-TZ", "sn", "sn-Latn", "sn-Latn-ZW", "xog", "xog-UG", "so-DJ", "so-ET", "so-KE", "nr", "nr-ZA", "st-LS", "es-BZ", "es-BR", "es-GQ", "es-PH", "zgh", "zgh-Tfng-MA", "zgh-Tfng", "ss", "ss-ZA", "ss-SZ", "sv-AX", "shi", "shi-Tfng", "shi-Tfng-MA", "shi-Latn", "shi-Latn-MA", "dav", "dav-KE", "ta-MY", "ta-SG", "twq", "twq-NE", "teo", "teo-KE", "teo-UG", "bo-IN", "tig", "tig-ER", "to", "to-TO", "tr-CY", "uz-Arab", "uz-Arab-AF", "vai", "vai-Vaii", "vai-Vaii-LR", "vai-Latn-LR", "vai-Latn", "vo", "vo-001", "vun", "vun-TZ", "wae", "wae-CH", "wal", "wal-ET", "yav", "yav-CM", "yo-BJ", "dje", "dje-NE", }, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 3081: {tags: []string{"en-AU"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 10249: {tags: []string{"en-BZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 4105: {tags: []string{"en-CA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 9225: {tags: []string{"en-029"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 15369: {tags: []string{"en-HK"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 16393: {tags: []string{"en-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 6153: {tags: []string{"en-IE"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 8201: {tags: []string{"en-JM"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 17417: {tags: []string{"en-MY"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 5129: {tags: []string{"en-NZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 13321: {tags: []string{"en-PH"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 18441: {tags: []string{"en-SG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 7177: {tags: []string{"en-ZA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 11273: {tags: []string{"en-TT"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 19465: {tags: []string{"en-AE"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 2057: {tags: []string{"en-GB"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 1033: {tags: []string{"en-US"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 12297: {tags: []string{"en-ZW"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 37: {tags: []string{"et"}, localMonth: localMonthsNameEstonian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEstonian, weekdayNamesAbbr: weekdayNamesEstonianAbbr}, 1061: {tags: []string{"et-EE"}, localMonth: localMonthsNameEstonian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEstonian, weekdayNamesAbbr: weekdayNamesEstonianAbbr}, 56: {tags: []string{"fo"}, localMonth: localMonthsNameFaroese, apFmt: apFmtFaroese, weekdayNames: weekdayNamesFaroese, weekdayNamesAbbr: weekdayNamesFaroeseAbbr}, 1080: {tags: []string{"fo-FO"}, localMonth: localMonthsNameFaroese, apFmt: apFmtFaroese, weekdayNames: weekdayNamesFaroese, weekdayNamesAbbr: weekdayNamesFaroeseAbbr}, 100: {tags: []string{"fil"}, localMonth: localMonthsNameFilipino, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFilipino, weekdayNamesAbbr: weekdayNamesFilipinoAbbr}, 1124: {tags: []string{"fil-PH"}, localMonth: localMonthsNameFilipino, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFilipino, weekdayNamesAbbr: weekdayNamesFilipinoAbbr}, 11: {tags: []string{"fi"}, localMonth: localMonthsNameFinnish, apFmt: apFmtFinnish, weekdayNames: weekdayNamesFinnish, weekdayNamesAbbr: weekdayNamesFinnishAbbr}, 1035: {tags: []string{"fi-FI"}, localMonth: localMonthsNameFinnish, apFmt: apFmtFinnish, weekdayNames: weekdayNamesFinnish, weekdayNamesAbbr: weekdayNamesFinnishAbbr}, 12: {tags: []string{"fr"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 2060: {tags: []string{"fr-BE"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 11276: {tags: []string{"fr-CM"}, localMonth: localMonthsNameFrench, apFmt: apFmtCameroon, weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 3084: {tags: []string{"fr-CA"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 7180: {tags: []string{"fr-029"}, localMonth: localMonthsNameCaribbean, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 9228: {tags: []string{"fr-CD"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 12300: {tags: []string{"fr-CI"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 1036: {tags: []string{"fr-FR"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 15372: {tags: []string{"fr-HT"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 5132: {tags: []string{"fr-LU"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 13324: {tags: []string{"fr-ML"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 14348: {tags: []string{"fr-MA"}, localMonth: localMonthsNameMorocco, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 6156: {tags: []string{"fr-MC"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 8204: {tags: []string{"fr-RE"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 10252: {tags: []string{"fr-SN"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 4108: {tags: []string{"fr-CH"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr}, 98: {tags: []string{"fy"}, localMonth: localMonthsNameFrisian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrisian, weekdayNamesAbbr: weekdayNamesFrisianAbbr}, 1122: {tags: []string{"fy-NL"}, localMonth: localMonthsNameFrisian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrisian, weekdayNamesAbbr: weekdayNamesFrisianAbbr}, 103: {tags: []string{"ff"}, localMonth: localMonthsNameFulah, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFulah, weekdayNamesAbbr: weekdayNamesFulahAbbr}, 31847: {tags: []string{"ff-Latn"}, localMonth: localMonthsNameFulah, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFulah, weekdayNamesAbbr: weekdayNamesFulahAbbr}, 1127: {tags: []string{"ff-NG", "ff-Latn-NG"}, localMonth: localMonthsNameNigeria, apFmt: apFmtNigeria, weekdayNames: weekdayNamesNigeria, weekdayNamesAbbr: weekdayNamesNigeriaAbbr}, 2151: {tags: []string{"ff-SN", "ff-Latn-SN"}, localMonth: localMonthsNameNigeria, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesNigeria, weekdayNamesAbbr: weekdayNamesNigeriaAbbr}, 86: {tags: []string{"gl"}, localMonth: localMonthsNameGalician, apFmt: apFmtCuba, weekdayNames: weekdayNamesGalician, weekdayNamesAbbr: weekdayNamesGalicianAbbr}, 1110: {tags: []string{"gl-ES"}, localMonth: localMonthsNameGalician, apFmt: apFmtCuba, weekdayNames: weekdayNamesGalician, weekdayNamesAbbr: weekdayNamesGalicianAbbr}, 55: {tags: []string{"ka"}, localMonth: localMonthsNameGeorgian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGeorgian, weekdayNamesAbbr: weekdayNamesGeorgianAbbr}, 1079: {tags: []string{"ka-GE"}, localMonth: localMonthsNameGeorgian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGeorgian, weekdayNamesAbbr: weekdayNamesGeorgianAbbr}, 7: {tags: []string{"de"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 3079: {tags: []string{"de-AT"}, localMonth: localMonthsNameAustria, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 1031: {tags: []string{"de-DE"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 5127: {tags: []string{"de-LI"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 4103: {tags: []string{"de-LU"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 2055: {tags: []string{"de-CH"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr}, 8: {tags: []string{"el"}, localMonth: localMonthsNameGreek, apFmt: apFmtGreek, weekdayNames: weekdayNamesGreek, weekdayNamesAbbr: weekdayNamesGreekAbbr}, 1032: {tags: []string{"el-GR"}, localMonth: localMonthsNameGreek, apFmt: apFmtGreek, weekdayNames: weekdayNamesGreek, weekdayNamesAbbr: weekdayNamesGreekAbbr}, 111: {tags: []string{"kl"}, localMonth: localMonthsNameGreenlandic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGreenlandic, weekdayNamesAbbr: weekdayNamesGreenlandicAbbr}, 1135: {tags: []string{"kl-GL"}, localMonth: localMonthsNameGreenlandic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGreenlandic, weekdayNamesAbbr: weekdayNamesGreenlandicAbbr}, 116: {tags: []string{"gn"}, localMonth: localMonthsNameGuarani, apFmt: apFmtCuba, weekdayNames: weekdayNamesGuarani, weekdayNamesAbbr: weekdayNamesGuaraniAbbr}, 1140: {tags: []string{"gn-PY"}, localMonth: localMonthsNameGuarani, apFmt: apFmtCuba, weekdayNames: weekdayNamesGuarani, weekdayNamesAbbr: weekdayNamesGuaraniAbbr}, 71: {tags: []string{"gu"}, localMonth: localMonthsNameGujarati, apFmt: apFmtGujarati, weekdayNames: weekdayNamesGujarati, weekdayNamesAbbr: weekdayNamesGujaratiAbbr}, 1095: {tags: []string{"gu-IN"}, localMonth: localMonthsNameGujarati, apFmt: apFmtGujarati, weekdayNames: weekdayNamesGujarati, weekdayNamesAbbr: weekdayNamesGujaratiAbbr}, 104: {tags: []string{"ha"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr}, 31848: {tags: []string{"ha-Latn"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr}, 1128: {tags: []string{"ha-Latn-NG"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr}, 117: {tags: []string{"haw"}, localMonth: localMonthsNameHawaiian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHawaiian, weekdayNamesAbbr: weekdayNamesHawaiianAbbr}, 1141: {tags: []string{"haw-US"}, localMonth: localMonthsNameHawaiian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHawaiian, weekdayNamesAbbr: weekdayNamesHawaiianAbbr}, 13: {tags: []string{"he"}, localMonth: localMonthsNameHebrew, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHebrew, weekdayNamesAbbr: weekdayNamesHebrewAbbr}, 1037: {tags: []string{"he-IL"}, localMonth: localMonthsNameHebrew, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHebrew, weekdayNamesAbbr: weekdayNamesHebrewAbbr}, 57: {tags: []string{"hi"}, localMonth: localMonthsNameHindi, apFmt: apFmtHindi, weekdayNames: weekdayNamesHindi, weekdayNamesAbbr: weekdayNamesHindiAbbr}, 1081: {tags: []string{"hi-IN"}, localMonth: localMonthsNameHindi, apFmt: apFmtHindi, weekdayNames: weekdayNamesHindi, weekdayNamesAbbr: weekdayNamesHindiAbbr}, 14: {tags: []string{"hu"}, localMonth: localMonthsNameHungarian, apFmt: apFmtHungarian, weekdayNames: weekdayNamesHungarian, weekdayNamesAbbr: weekdayNamesHungarianAbbr}, 1038: {tags: []string{"hu-HU"}, localMonth: localMonthsNameHungarian, apFmt: apFmtHungarian, weekdayNames: weekdayNamesHungarian, weekdayNamesAbbr: weekdayNamesHungarianAbbr}, 15: {tags: []string{"is"}, localMonth: localMonthsNameIcelandic, apFmt: apFmtIcelandic, weekdayNames: weekdayNamesIcelandic, weekdayNamesAbbr: weekdayNamesIcelandicAbbr}, 1039: {tags: []string{"is-IS"}, localMonth: localMonthsNameIcelandic, apFmt: apFmtIcelandic, weekdayNames: weekdayNamesIcelandic, weekdayNamesAbbr: weekdayNamesIcelandicAbbr}, 112: {tags: []string{"ig"}, localMonth: localMonthsNameIgbo, apFmt: apFmtIgbo, weekdayNames: weekdayNamesIgbo, weekdayNamesAbbr: weekdayNamesIgboAbbr}, 1136: {tags: []string{"ig-NG"}, localMonth: localMonthsNameIgbo, apFmt: apFmtIgbo, weekdayNames: weekdayNamesIgbo, weekdayNamesAbbr: weekdayNamesIgboAbbr}, 33: {tags: []string{"id"}, localMonth: localMonthsNameIndonesian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesIndonesian, weekdayNamesAbbr: weekdayNamesIndonesianAbbr}, 1057: {tags: []string{"id-ID"}, localMonth: localMonthsNameIndonesian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesIndonesian, weekdayNamesAbbr: weekdayNamesIndonesianAbbr}, 93: {tags: []string{"iu"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr}, 31837: {tags: []string{"iu-Latn"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr}, 2141: {tags: []string{"iu-Latn-CA"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr}, 30813: {tags: []string{"iu-Cans"}, localMonth: localMonthsNameSyllabics, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSyllabics, weekdayNamesAbbr: weekdayNamesSyllabicsAbbr}, 1117: {tags: []string{"iu-Cans-CA"}, localMonth: localMonthsNameSyllabics, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSyllabics, weekdayNamesAbbr: weekdayNamesSyllabicsAbbr}, 60: {tags: []string{"ga"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish, weekdayNames: weekdayNamesIrish, weekdayNamesAbbr: weekdayNamesIrishAbbr}, 2108: {tags: []string{"ga-IE"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish, weekdayNames: weekdayNamesIrish, weekdayNamesAbbr: weekdayNamesIrishAbbr}, 16: {tags: []string{"it"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr}, 1040: {tags: []string{"it-IT"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr}, 2064: {tags: []string{"it-CH"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr}, 17: {tags: []string{"ja"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr}, 1041: {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr}, 75: {tags: []string{"kn"}, localMonth: localMonthsNameKannada, apFmt: apFmtKannada, weekdayNames: weekdayNamesKannada, weekdayNamesAbbr: weekdayNamesKannadaAbbr}, 1099: {tags: []string{"kn-IN"}, localMonth: localMonthsNameKannada, apFmt: apFmtKannada, weekdayNames: weekdayNamesKannada, weekdayNamesAbbr: weekdayNamesKannadaAbbr}, 1137: {tags: []string{"kr-Latn-NG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 96: {tags: []string{"ks"}, localMonth: localMonthsNameKashmiri, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKashmiri, weekdayNamesAbbr: weekdayNamesKashmiriAbbr}, 1120: {tags: []string{"ks-Arab"}, localMonth: localMonthsNameKashmiri, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKashmiri, weekdayNamesAbbr: weekdayNamesKashmiriAbbr}, 2144: {tags: []string{"ks-Deva-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 63: {tags: []string{"kk"}, localMonth: localMonthsNameKazakh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKazakh, weekdayNamesAbbr: weekdayNamesKazakhAbbr}, 1087: {tags: []string{"kk-KZ"}, localMonth: localMonthsNameKazakh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKazakh, weekdayNamesAbbr: weekdayNamesKazakhAbbr}, 83: {tags: []string{"km"}, localMonth: localMonthsNameKhmer, apFmt: apFmtKhmer, weekdayNames: weekdayNamesKhmer, weekdayNamesAbbr: weekdayNamesKhmerAbbr}, 1107: {tags: []string{"km-KH"}, localMonth: localMonthsNameKhmer, apFmt: apFmtKhmer, weekdayNames: weekdayNamesKhmer, weekdayNamesAbbr: weekdayNamesKhmerAbbr}, 134: {tags: []string{"quc"}, localMonth: localMonthsNameKiche, apFmt: apFmtCuba, weekdayNames: weekdayNamesKiche, weekdayNamesAbbr: weekdayNamesKicheAbbr}, 1158: {tags: []string{"quc-Latn-GT"}, localMonth: localMonthsNameKiche, apFmt: apFmtCuba, weekdayNames: weekdayNamesKiche, weekdayNamesAbbr: weekdayNamesKicheAbbr}, 135: {tags: []string{"rw"}, localMonth: localMonthsNameKinyarwanda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKinyarwanda, weekdayNamesAbbr: weekdayNamesKinyarwandaAbbr}, 1159: {tags: []string{"rw-RW"}, localMonth: localMonthsNameKinyarwanda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKinyarwanda, weekdayNamesAbbr: weekdayNamesKinyarwandaAbbr}, 65: {tags: []string{"sw"}, localMonth: localMonthsNameKiswahili, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKiswahili, weekdayNamesAbbr: weekdayNamesKiswahiliAbbr}, 1089: {tags: []string{"sw-KE"}, localMonth: localMonthsNameKiswahili, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKiswahili, weekdayNamesAbbr: weekdayNamesKiswahiliAbbr}, 87: {tags: []string{"kok"}, localMonth: localMonthsNameKonkani, apFmt: apFmtKonkani, weekdayNames: weekdayNamesKonkani, weekdayNamesAbbr: weekdayNamesKonkaniAbbr}, 1111: {tags: []string{"kok-IN"}, localMonth: localMonthsNameKonkani, apFmt: apFmtKonkani, weekdayNames: weekdayNamesKonkani, weekdayNamesAbbr: weekdayNamesKonkaniAbbr}, 18: {tags: []string{"ko"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean, weekdayNames: weekdayNamesKorean, weekdayNamesAbbr: weekdayNamesKoreanAbbr}, 1042: {tags: []string{"ko-KR"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean, weekdayNames: weekdayNamesKorean, weekdayNamesAbbr: weekdayNamesKoreanAbbr}, 64: {tags: []string{"ky"}, localMonth: localMonthsNameKyrgyz, apFmt: apFmtKyrgyz, weekdayNames: weekdayNamesKyrgyz, weekdayNamesAbbr: weekdayNamesKyrgyzAbbr}, 1088: {tags: []string{"ky-KG"}, localMonth: localMonthsNameKyrgyz, apFmt: apFmtKyrgyz, weekdayNames: weekdayNamesKyrgyz, weekdayNamesAbbr: weekdayNamesKyrgyzAbbr}, 84: {tags: []string{"lo"}, localMonth: localMonthsNameLao, apFmt: apFmtLao, weekdayNames: weekdayNamesLao, weekdayNamesAbbr: weekdayNamesLaoAbbr}, 1108: {tags: []string{"lo-LA"}, localMonth: localMonthsNameLao, apFmt: apFmtLao, weekdayNames: weekdayNamesLao, weekdayNamesAbbr: weekdayNamesLaoAbbr}, 1142: {tags: []string{"la-VA"}, localMonth: localMonthsNameLatin, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLatin, weekdayNamesAbbr: weekdayNamesLatinAbbr}, 38: {tags: []string{"lv"}, localMonth: localMonthsNameLatvian, apFmt: apFmtLatvian, weekdayNames: weekdayNamesLatvian, weekdayNamesAbbr: weekdayNamesLatvianAbbr}, 1062: {tags: []string{"lv-LV"}, localMonth: localMonthsNameLatvian, apFmt: apFmtLatvian, weekdayNames: weekdayNamesLatvian, weekdayNamesAbbr: weekdayNamesLatvianAbbr}, 39: {tags: []string{"lt"}, localMonth: localMonthsNameLithuanian, apFmt: apFmtLithuanian, weekdayNames: weekdayNamesLithuanian, weekdayNamesAbbr: weekdayNamesLithuanianAbbr}, 1063: {tags: []string{"lt-LT"}, localMonth: localMonthsNameLithuanian, apFmt: apFmtLithuanian, weekdayNames: weekdayNamesLithuanian, weekdayNamesAbbr: weekdayNamesLithuanianAbbr}, 31790: {tags: []string{"dsb"}, localMonth: localMonthsNameLowerSorbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLowerSorbian, weekdayNamesAbbr: weekdayNamesLowerSorbianAbbr}, 2094: {tags: []string{"dsb-DE"}, localMonth: localMonthsNameLowerSorbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLowerSorbian, weekdayNamesAbbr: weekdayNamesLowerSorbianAbbr}, 110: {tags: []string{"lb"}, localMonth: localMonthsNameLuxembourgish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLuxembourgish, weekdayNamesAbbr: weekdayNamesLuxembourgishAbbr}, 1134: {tags: []string{"lb-LU"}, localMonth: localMonthsNameLuxembourgish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLuxembourgish, weekdayNamesAbbr: weekdayNamesLuxembourgishAbbr}, 47: {tags: []string{"mk"}, localMonth: localMonthsNameMacedonian, apFmt: apFmtMacedonian, weekdayNames: weekdayNamesMacedonian, weekdayNamesAbbr: weekdayNamesMacedonianAbbr}, 1071: {tags: []string{"mk-MK"}, localMonth: localMonthsNameMacedonian, apFmt: apFmtMacedonian, weekdayNames: weekdayNamesMacedonian, weekdayNamesAbbr: weekdayNamesMacedonianAbbr}, 62: {tags: []string{"ms"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr}, 2110: {tags: []string{"ms-BN"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr}, 1086: {tags: []string{"ms-MY"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr}, 76: {tags: []string{"ml"}, localMonth: localMonthsNameMalayalam, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMalayalam, weekdayNamesAbbr: weekdayNamesMalayalamAbbr}, 1100: {tags: []string{"ml-IN"}, localMonth: localMonthsNameMalayalam, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMalayalam, weekdayNamesAbbr: weekdayNamesMalayalamAbbr}, 58: {tags: []string{"mt"}, localMonth: localMonthsNameMaltese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMaltese, weekdayNamesAbbr: weekdayNamesMalteseAbbr}, 1082: {tags: []string{"mt-MT"}, localMonth: localMonthsNameMaltese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMaltese, weekdayNamesAbbr: weekdayNamesMalteseAbbr}, 129: {tags: []string{"mi"}, localMonth: localMonthsNameMaori, apFmt: apFmtCuba, weekdayNames: weekdayNamesMaori, weekdayNamesAbbr: weekdayNamesMaoriAbbr}, 1153: {tags: []string{"mi-NZ"}, localMonth: localMonthsNameMaori, apFmt: apFmtCuba, weekdayNames: weekdayNamesMaori, weekdayNamesAbbr: weekdayNamesMaoriAbbr}, 122: {tags: []string{"arn"}, localMonth: localMonthsNameMapudungun, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMapudungun, weekdayNamesAbbr: weekdayNamesMapudungunAbbr}, 1146: {tags: []string{"arn-CL"}, localMonth: localMonthsNameMapudungun, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMapudungun, weekdayNamesAbbr: weekdayNamesMapudungunAbbr}, 78: {tags: []string{"mr"}, localMonth: localMonthsNameMarathi, apFmt: apFmtKonkani, weekdayNames: weekdayNamesMarathi, weekdayNamesAbbr: weekdayNamesMarathiAbbr}, 1102: {tags: []string{"mr-IN"}, localMonth: localMonthsNameMarathi, apFmt: apFmtKonkani, weekdayNames: weekdayNamesMarathi, weekdayNamesAbbr: weekdayNamesMarathiAbbr}, 124: {tags: []string{"moh"}, localMonth: localMonthsNameMohawk, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMohawk, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 1148: {tags: []string{"moh-CA"}, localMonth: localMonthsNameMohawk, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMohawk, weekdayNamesAbbr: weekdayNamesEnglishAbbr}, 80: {tags: []string{"mn"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianAbbr}, 30800: {tags: []string{"mn-Cyrl"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianCyrlAbbr}, 1104: {tags: []string{"mn-MN"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianCyrlAbbr}, 31824: {tags: []string{"mn-Mong"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolian, weekdayNamesAbbr: weekdayNamesTraditionalMongolian}, 2128: {tags: []string{"mn-Mong-CN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolian, weekdayNamesAbbr: weekdayNamesTraditionalMongolian}, 3152: {tags: []string{"mn-Mong-MN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolianMN, weekdayNamesAbbr: weekdayNamesTraditionalMongolianMN}, 97: {tags: []string{"ne"}, localMonth: localMonthsNameNepali, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepali, weekdayNamesAbbr: weekdayNamesNepaliAbbr}, 2145: {tags: []string{"ne-IN"}, localMonth: localMonthsNameNepaliIN, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepaliIN, weekdayNamesAbbr: weekdayNamesNepaliINAbbr}, 1121: {tags: []string{"ne-NP"}, localMonth: localMonthsNameNepali, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepali, weekdayNamesAbbr: weekdayNamesNepaliAbbr}, 20: {tags: []string{"no"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianAbbr}, 31764: {tags: []string{"nb"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianNOAbbr}, 1044: {tags: []string{"nb-NO"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianNOAbbr}, 30740: {tags: []string{"nn"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtNorwegian, weekdayNames: weekdayNamesNorwegianNynorsk, weekdayNamesAbbr: weekdayNamesNorwegianNynorskAbbr}, 2068: {tags: []string{"nn-NO"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtNorwegian, weekdayNames: weekdayNamesNorwegianNynorsk, weekdayNamesAbbr: weekdayNamesNorwegianNynorskAbbr}, 130: {tags: []string{"oc"}, localMonth: localMonthsNameOccitan, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOccitan, weekdayNamesAbbr: weekdayNamesOccitanAbbr}, 1154: {tags: []string{"oc-FR"}, localMonth: localMonthsNameOccitan, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOccitan, weekdayNamesAbbr: weekdayNamesOccitanAbbr}, 72: {tags: []string{"or"}, localMonth: localMonthsNameOdia, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOdia, weekdayNamesAbbr: weekdayNamesOdiaAbbr}, 1096: {tags: []string{"or-IN"}, localMonth: localMonthsNameOdia, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOdia, weekdayNamesAbbr: weekdayNamesOdiaAbbr}, 114: {tags: []string{"om"}, localMonth: localMonthsNameOromo, apFmt: apFmtOromo, weekdayNames: weekdayNamesOromo, weekdayNamesAbbr: weekdayNamesOromoAbbr}, 1138: {tags: []string{"om-ET"}, localMonth: localMonthsNameOromo, apFmt: apFmtOromo, weekdayNames: weekdayNamesOromo, weekdayNamesAbbr: weekdayNamesOromoAbbr}, 99: {tags: []string{"ps"}, localMonth: localMonthsNamePashto, apFmt: apFmtPashto, weekdayNames: weekdayNamesPashto, weekdayNamesAbbr: weekdayNamesPashto}, 1123: {tags: []string{"ps-AF"}, localMonth: localMonthsNamePashto, apFmt: apFmtPashto, weekdayNames: weekdayNamesPashto, weekdayNamesAbbr: weekdayNamesPashto}, 41: {tags: []string{"fa"}, localMonth: localMonthsNamePersian, apFmt: apFmtPersian, weekdayNames: weekdayNamesPersian, weekdayNamesAbbr: weekdayNamesPersian}, 1065: {tags: []string{"fa-IR"}, localMonth: localMonthsNamePersian, apFmt: apFmtPersian, weekdayNames: weekdayNamesPersian, weekdayNamesAbbr: weekdayNamesPersian}, 21: {tags: []string{"pl"}, localMonth: localMonthsNamePolish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPolish, weekdayNamesAbbr: weekdayNamesPolishAbbr}, 1045: {tags: []string{"pl-PL"}, localMonth: localMonthsNamePolish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPolish, weekdayNamesAbbr: weekdayNamesPolishAbbr}, 22: {tags: []string{"pt"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr}, 1046: {tags: []string{"pt-BR"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr}, 2070: {tags: []string{"pt-PT"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr}, 70: {tags: []string{"pa"}, localMonth: localMonthsNamePunjabi, apFmt: apFmtPunjabi, weekdayNames: weekdayNamesPunjabi, weekdayNamesAbbr: weekdayNamesPunjabiAbbr}, 31814: {tags: []string{"pa-Arab"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPunjabiArab, weekdayNamesAbbr: weekdayNamesPunjabiArab}, 1094: {tags: []string{"pa-IN"}, localMonth: localMonthsNamePunjabi, apFmt: apFmtPunjabi, weekdayNames: weekdayNamesPunjabi, weekdayNamesAbbr: weekdayNamesPunjabiAbbr}, 2118: {tags: []string{"pa-Arab-PK"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPunjabiArab, weekdayNamesAbbr: weekdayNamesPunjabiArab}, 107: {tags: []string{"quz"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechua, weekdayNamesAbbr: weekdayNamesQuechuaAbbr}, 1131: {tags: []string{"quz-BO"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechua, weekdayNamesAbbr: weekdayNamesQuechuaAbbr}, 2155: {tags: []string{"quz-EC"}, localMonth: localMonthsNameQuechuaEcuador, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesQuechuaEcuador, weekdayNamesAbbr: weekdayNamesQuechuaEcuadorAbbr}, 3179: {tags: []string{"quz-PE"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechuaPeru, weekdayNamesAbbr: weekdayNamesQuechuaPeruAbbr}, 24: {tags: []string{"ro"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianAbbr}, 2072: {tags: []string{"ro-MD"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianMoldovaAbbr}, 1048: {tags: []string{"ro-RO"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianAbbr}, 23: {tags: []string{"rm"}, localMonth: localMonthsNameRomansh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRomansh, weekdayNamesAbbr: weekdayNamesRomanshAbbr}, 1047: {tags: []string{"rm-CH"}, localMonth: localMonthsNameRomansh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRomansh, weekdayNamesAbbr: weekdayNamesRomanshAbbr}, 25: {tags: []string{"ru"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr}, 2073: {tags: []string{"ru-MD"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr}, 1049: {tags: []string{"ru-RU"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr}, 133: {tags: []string{"sah"}, localMonth: localMonthsNameSakha, apFmt: apFmtSakha, weekdayNames: weekdayNamesSakha, weekdayNamesAbbr: weekdayNamesSakhaAbbr}, 1157: {tags: []string{"sah-RU"}, localMonth: localMonthsNameSakha, apFmt: apFmtSakha, weekdayNames: weekdayNamesSakha, weekdayNamesAbbr: weekdayNamesSakhaAbbr}, 28731: {tags: []string{"smn"}, localMonth: localMonthsNameSami, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSami, weekdayNamesAbbr: weekdayNamesSamiAbbr}, 9275: {tags: []string{"smn-FI"}, localMonth: localMonthsNameSami, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSami, weekdayNamesAbbr: weekdayNamesSamiAbbr}, 31803: {tags: []string{"smj"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSamiLule, weekdayNamesAbbr: weekdayNamesSamiSwedenAbbr}, 4155: {tags: []string{"smj-NO"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSamiLule, weekdayNamesAbbr: weekdayNamesSamiSamiLuleAbbr}, 5179: {tags: []string{"smj-SE"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSweden, weekdayNamesAbbr: weekdayNamesSamiSwedenAbbr}, 59: {tags: []string{"se"}, localMonth: localMonthsNameSamiNorthern, apFmt: apFmtSamiNorthern, weekdayNames: weekdayNamesSamiNorthern, weekdayNamesAbbr: weekdayNamesSamiNorthernAbbr}, 3131: {tags: []string{"se-FI"}, localMonth: localMonthsNameSamiNorthernFI, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiNorthernFI, weekdayNamesAbbr: weekdayNamesSamiNorthernFIAbbr}, 1083: {tags: []string{"se-NO"}, localMonth: localMonthsNameSamiNorthern, apFmt: apFmtSamiNorthern, weekdayNames: weekdayNamesSamiNorthern, weekdayNamesAbbr: weekdayNamesSamiNorthernAbbr}, 2107: {tags: []string{"se-SE"}, localMonth: localMonthsNameSamiNorthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiNorthernSE, weekdayNamesAbbr: weekdayNamesSamiNorthernSEAbbr}, 29755: {tags: []string{"sms"}, localMonth: localMonthsNameSamiSkolt, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSkolt, weekdayNamesAbbr: weekdayNamesSamiSkoltAbbr}, 8251: {tags: []string{"sms-FI"}, localMonth: localMonthsNameSamiSkolt, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSkolt, weekdayNamesAbbr: weekdayNamesSamiSkoltAbbr}, 30779: {tags: []string{"sma"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr}, 6203: {tags: []string{"sma-NO"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr}, 7227: {tags: []string{"sma-SE"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr}, 79: {tags: []string{"sa"}, localMonth: localMonthsNameSanskrit, apFmt: apFmtSanskrit, weekdayNames: weekdayNamesSanskrit, weekdayNamesAbbr: weekdayNamesSanskritAbbr}, 1103: {tags: []string{"sa-IN"}, localMonth: localMonthsNameSanskrit, apFmt: apFmtSanskrit, weekdayNames: weekdayNamesSanskrit, weekdayNamesAbbr: weekdayNamesSanskritAbbr}, 145: {tags: []string{"gd"}, localMonth: localMonthsNameScottishGaelic, apFmt: apFmtScottishGaelic, weekdayNames: weekdayNamesGaelic, weekdayNamesAbbr: weekdayNamesGaelicAbbr}, 1169: {tags: []string{"gd-GB"}, localMonth: localMonthsNameScottishGaelic, apFmt: apFmtScottishGaelic, weekdayNames: weekdayNamesGaelic, weekdayNamesAbbr: weekdayNamesGaelicAbbr}, 27674: {tags: []string{"sr-Cyrl"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr}, 7194: {tags: []string{"sr-Cyrl-BA"}, localMonth: localMonthsNameSerbianBA, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianBA, weekdayNamesAbbr: weekdayNamesSerbianBAAbbr}, 12314: {tags: []string{"sr-Cyrl-ME"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianME, weekdayNamesAbbr: weekdayNamesSerbianBAAbbr}, 10266: {tags: []string{"sr-Cyrl-RS"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr}, 3098: {tags: []string{"sr-Cyrl-CS"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr}, 28698: {tags: []string{"sr-Latn"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr}, 31770: {tags: []string{"sr"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr}, 6170: {tags: []string{"sr-Latn-BA"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatinBA, weekdayNames: weekdayNamesSerbianLatinBA, weekdayNamesAbbr: weekdayNamesSerbianLatinBAAbbr}, 11290: {tags: []string{"sr-Latn-ME"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatinBA, weekdayNames: weekdayNamesSerbianLatinME, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr}, 9242: {tags: []string{"sr-Latn-RS"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr}, 2074: {tags: []string{"sr-Latn-CS"}, localMonth: localMonthsNameSerbianLatinCS, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinCSAbbr}, 108: {tags: []string{"nso"}, localMonth: localMonthsNameSesothoSaLeboa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSesothoSaLeboa, weekdayNamesAbbr: weekdayNamesSesothoSaLeboaAbbr}, 1132: {tags: []string{"nso-ZA"}, localMonth: localMonthsNameSesothoSaLeboa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSesothoSaLeboa, weekdayNamesAbbr: weekdayNamesSesothoSaLeboaAbbr}, 50: {tags: []string{"tn"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr}, 2098: {tags: []string{"tn-BW"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr}, 1074: {tags: []string{"tn-ZA"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr}, 89: {tags: []string{"sd"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr}, 31833: {tags: []string{"sd-Arab"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr}, 2137: {tags: []string{"sd-Arab-PK"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr}, 91: {tags: []string{"si"}, localMonth: localMonthsNameSinhala, apFmt: apFmtSinhala, weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr}, 1115: {tags: []string{"si-LK"}, localMonth: localMonthsNameSinhala, apFmt: apFmtSinhala, weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr}, 27: {tags: []string{"sk"}, localMonth: localMonthsNameSlovak, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSlovak, weekdayNamesAbbr: weekdayNamesSlovakAbbr}, 1051: {tags: []string{"sk-SK"}, localMonth: localMonthsNameSlovak, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSlovak, weekdayNamesAbbr: weekdayNamesSlovakAbbr}, 36: {tags: []string{"sl"}, localMonth: localMonthsNameSlovenian, apFmt: apFmtSlovenian, weekdayNames: weekdayNamesSlovenian, weekdayNamesAbbr: weekdayNamesSlovenianAbbr}, 1060: {tags: []string{"sl-SI"}, localMonth: localMonthsNameSlovenian, apFmt: apFmtSlovenian, weekdayNames: weekdayNamesSlovenian, weekdayNamesAbbr: weekdayNamesSlovenianAbbr}, 119: {tags: []string{"so"}, localMonth: localMonthsNameSomali, apFmt: apFmtSomali, weekdayNames: weekdayNamesSomali, weekdayNamesAbbr: weekdayNamesSomaliAbbr}, 1143: {tags: []string{"so-SO"}, localMonth: localMonthsNameSomali, apFmt: apFmtSomali, weekdayNames: weekdayNamesSomali, weekdayNamesAbbr: weekdayNamesSomaliAbbr}, 48: {tags: []string{"st"}, localMonth: localMonthsNameSotho, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSotho, weekdayNamesAbbr: weekdayNamesSothoAbbr}, 1072: {tags: []string{"st-ZA"}, localMonth: localMonthsNameSotho, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSotho, weekdayNamesAbbr: weekdayNamesSothoAbbr}, 10: {tags: []string{"es"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr}, 11274: {tags: []string{"es-AR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 8202: {tags: []string{"es-VE"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 16394: {tags: []string{"es-BO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 13322: {tags: []string{"es-CL"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 9226: {tags: []string{"es-CO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 5130: {tags: []string{"es-CR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 23562: {tags: []string{"es-CU"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 7178: {tags: []string{"es-DO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 12298: {tags: []string{"es-EC"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 17418: {tags: []string{"es-SV"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 4106: {tags: []string{"es-GT"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 18442: {tags: []string{"es-HN"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 22538: {tags: []string{"es-419"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 2058: {tags: []string{"es-MX"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 19466: {tags: []string{"es-NI"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 6154: {tags: []string{"es-PA"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 15370: {tags: []string{"es-PY"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 10250: {tags: []string{"es-PE"}, localMonth: localMonthsNameSpanishPE, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 20490: {tags: []string{"es-PR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 1034: {tags: []string{"es-ES_tradnl"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr}, 3082: {tags: []string{"es-ES"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr}, 21514: {tags: []string{"es-US"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishUSAbbr}, 14346: {tags: []string{"es-UY"}, localMonth: localMonthsNameSpanishPE, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr}, 29: {tags: []string{"sv"}, localMonth: localMonthsNameSwedish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr}, 2077: {tags: []string{"sv-FI"}, localMonth: localMonthsNameSwedishFI, apFmt: apFmtSwedish, weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr}, 1053: {tags: []string{"sv-SE"}, localMonth: localMonthsNameSwedishFI, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr}, 90: {tags: []string{"syr"}, localMonth: localMonthsNameSyriac, apFmt: apFmtSyriac, weekdayNames: weekdayNamesSyriac, weekdayNamesAbbr: weekdayNamesSyriacAbbr}, 1114: {tags: []string{"syr-SY"}, localMonth: localMonthsNameSyriac, apFmt: apFmtSyriac, weekdayNames: weekdayNamesSyriac, weekdayNamesAbbr: weekdayNamesSyriacAbbr}, 40: {tags: []string{"tg"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr}, 31784: {tags: []string{"tg-Cyrl"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr}, 1064: {tags: []string{"tg-Cyrl-TJ"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr}, 95: {tags: []string{"tzm"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr}, 31839: {tags: []string{"tzm-Latn"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr}, 2143: {tags: []string{"tzm-Latn-DZ"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr}, 73: {tags: []string{"ta"}, localMonth: localMonthsNameTamil, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamil, weekdayNamesAbbr: weekdayNamesTamilAbbr}, 1097: {tags: []string{"ta-IN"}, localMonth: localMonthsNameTamil, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamil, weekdayNamesAbbr: weekdayNamesTamilAbbr}, 2121: {tags: []string{"ta-LK"}, localMonth: localMonthsNameTamilLK, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamilLK, weekdayNamesAbbr: weekdayNamesTamilLKAbbr}, 68: {tags: []string{"tt"}, localMonth: localMonthsNameTatar, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTatar, weekdayNamesAbbr: weekdayNamesTatarAbbr}, 1092: {tags: []string{"tt-RU"}, localMonth: localMonthsNameTatar, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTatar, weekdayNamesAbbr: weekdayNamesTatarAbbr}, 74: {tags: []string{"te"}, localMonth: localMonthsNameTelugu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTelugu, weekdayNamesAbbr: weekdayNamesTeluguAbbr}, 1098: {tags: []string{"te-IN"}, localMonth: localMonthsNameTelugu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTelugu, weekdayNamesAbbr: weekdayNamesTeluguAbbr}, 30: {tags: []string{"th"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesThai, weekdayNamesAbbr: weekdayNamesThaiAbbr}, 1054: {tags: []string{"th-TH"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesThai, weekdayNamesAbbr: weekdayNamesThaiAbbr}, 81: {tags: []string{"bo"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan, weekdayNames: weekdayNamesTibetan, weekdayNamesAbbr: weekdayNamesTibetanAbbr}, 1105: {tags: []string{"bo-CN"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan, weekdayNames: weekdayNamesTibetan, weekdayNamesAbbr: weekdayNamesTibetanAbbr}, 115: {tags: []string{"ti"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinya, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr}, 2163: {tags: []string{"ti-ER"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinyaER, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr}, 1139: {tags: []string{"ti-ET"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinya, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr}, 49: {tags: []string{"ts"}, localMonth: localMonthsNameTsonga, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTsonga, weekdayNamesAbbr: weekdayNamesTsongaAbbr}, 1073: {tags: []string{"ts-ZA"}, localMonth: localMonthsNameTsonga, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTsonga, weekdayNamesAbbr: weekdayNamesTsongaAbbr}, 31: {tags: []string{"tr"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish, weekdayNames: weekdayNamesTurkish, weekdayNamesAbbr: weekdayNamesTurkishAbbr}, 1055: {tags: []string{"tr-TR"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish, weekdayNames: weekdayNamesTurkish, weekdayNamesAbbr: weekdayNamesTurkishAbbr}, 66: {tags: []string{"tk"}, localMonth: localMonthsNameTurkmen, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTurkmen, weekdayNamesAbbr: weekdayNamesTurkmenAbbr}, 1090: {tags: []string{"tk-TM"}, localMonth: localMonthsNameTurkmen, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTurkmen, weekdayNamesAbbr: weekdayNamesTurkmenAbbr}, 34: {tags: []string{"uk"}, localMonth: localMonthsNameUkrainian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUkrainian, weekdayNamesAbbr: weekdayNamesUkrainianAbbr}, 1058: {tags: []string{"uk-UA"}, localMonth: localMonthsNameUkrainian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUkrainian, weekdayNamesAbbr: weekdayNamesUkrainianAbbr}, 46: {tags: []string{"hsb"}, localMonth: localMonthsNameUpperSorbian, apFmt: apFmtUpperSorbian, weekdayNames: weekdayNamesSorbian, weekdayNamesAbbr: weekdayNamesSorbianAbbr}, 1070: {tags: []string{"hsb-DE"}, localMonth: localMonthsNameUpperSorbian, apFmt: apFmtUpperSorbian, weekdayNames: weekdayNamesSorbian, weekdayNamesAbbr: weekdayNamesSorbianAbbr}, 32: {tags: []string{"ur"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUrdu, weekdayNamesAbbr: weekdayNamesUrdu}, 2080: {tags: []string{"ur-IN"}, localMonth: localMonthsNamePunjabiArab, apFmt: apFmtUrdu, weekdayNames: weekdayNamesUrduIN, weekdayNamesAbbr: weekdayNamesUrduIN}, 1056: {tags: []string{"ur-PK"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUrdu, weekdayNamesAbbr: weekdayNamesUrdu}, 128: {tags: []string{"ug"}, localMonth: localMonthsNameUyghur, apFmt: apFmtUyghur, weekdayNames: weekdayNamesUyghur, weekdayNamesAbbr: weekdayNamesUyghurAbbr}, 1152: {tags: []string{"ug-CN"}, localMonth: localMonthsNameUyghur, apFmt: apFmtUyghur, weekdayNames: weekdayNamesUyghur, weekdayNamesAbbr: weekdayNamesUyghurAbbr}, 30787: {tags: []string{"uz-Cyrl"}, localMonth: localMonthsNameUzbekCyrillic, apFmt: apFmtUzbekCyrillic, weekdayNames: weekdayNamesUzbekCyrillic, weekdayNamesAbbr: weekdayNamesUzbekCyrillicAbbr}, 2115: {tags: []string{"uz-Cyrl-UZ"}, localMonth: localMonthsNameUzbekCyrillic, apFmt: apFmtUzbekCyrillic, weekdayNames: weekdayNamesUzbekCyrillic, weekdayNamesAbbr: weekdayNamesUzbekCyrillicAbbr}, 67: {tags: []string{"uz"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr}, 31811: {tags: []string{"uz-Latn"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr}, 1091: {tags: []string{"uz-Latn-UZ"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr}, 2051: {tags: []string{"ca-ES-valencia"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr}, 51: {tags: []string{"ve"}, localMonth: localMonthsNameVenda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesVenda, weekdayNamesAbbr: weekdayNamesVendaAbbr}, 1075: {tags: []string{"ve-ZA"}, localMonth: localMonthsNameVenda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesVenda, weekdayNamesAbbr: weekdayNamesVendaAbbr}, 42: {tags: []string{"vi"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese, weekdayNames: weekdayNamesVietnamese, weekdayNamesAbbr: weekdayNamesVietnameseAbbr}, 1066: {tags: []string{"vi-VN"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese, weekdayNames: weekdayNamesVietnamese, weekdayNamesAbbr: weekdayNamesVietnameseAbbr}, 82: {tags: []string{"cy"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh, weekdayNames: weekdayNamesWelsh, weekdayNamesAbbr: weekdayNamesWelshAbbr}, 1106: {tags: []string{"cy-GB"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh, weekdayNames: weekdayNamesWelsh, weekdayNamesAbbr: weekdayNamesWelshAbbr}, 136: {tags: []string{"wo"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof, weekdayNames: weekdayNamesWolof, weekdayNamesAbbr: weekdayNamesWolofAbbr}, 1160: {tags: []string{"wo-SN"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof, weekdayNames: weekdayNamesWolof, weekdayNamesAbbr: weekdayNamesWolofAbbr}, 52: {tags: []string{"xh"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesXhosa, weekdayNamesAbbr: weekdayNamesXhosaAbbr}, 1076: {tags: []string{"xh-ZA"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesXhosa, weekdayNamesAbbr: weekdayNamesXhosaAbbr}, 120: {tags: []string{"ii"}, localMonth: localMonthsNameYi, apFmt: apFmtYi, weekdayNames: weekdayNamesYi, weekdayNamesAbbr: weekdayNamesYiAbbr}, 1144: {tags: []string{"ii-CN"}, localMonth: localMonthsNameYi, apFmt: apFmtYi, weekdayNames: weekdayNamesYi, weekdayNamesAbbr: weekdayNamesYiAbbr}, 1085: {tags: []string{"yi-001"}, localMonth: localMonthsNameYiddish, apFmt: apFmtYiddish, weekdayNames: weekdayNamesYiddish, weekdayNamesAbbr: weekdayNamesYiddishAbbr}, 106: {tags: []string{"yo"}, localMonth: localMonthsNameYoruba, apFmt: apFmtYoruba, weekdayNames: weekdayNamesYoruba, weekdayNamesAbbr: weekdayNamesYorubaAbbr}, 1130: {tags: []string{"yo-NG"}, localMonth: localMonthsNameYoruba, apFmt: apFmtYoruba, weekdayNames: weekdayNamesYoruba, weekdayNamesAbbr: weekdayNamesYorubaAbbr}, 53: {tags: []string{"zu"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesZulu, weekdayNamesAbbr: weekdayNamesZuluAbbr}, 1077: {tags: []string{"zu-ZA"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesZulu, weekdayNamesAbbr: weekdayNamesZuluAbbr}, } // supportedLanguageCodeInfo directly maps the supported language code and // tags. supportedLanguageCodeInfo = map[string]languageInfo{ "JA-JP-X-GANNEN": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr}, "JA-JP-X-GANNEN,80": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr, useGannen: true}, } // republicOfChinaYear defined start time of the Republic of China republicOfChinaYear = time.Date(1912, time.January, 1, 0, 0, 0, 0, time.UTC) // republicOfChinaEraName defined the Republic of China era name for the // Republic of China calendar. republicOfChinaEraName = []string{"\u4E2D\u83EF\u6C11\u570B", "\u6C11\u570B", "\u524D"} // japaneseEraYears list the Japanese era name periods. japaneseEraYears = []time.Time{ time.Date(1868, time.August, 8, 0, 0, 0, 0, time.UTC), time.Date(1912, time.June, 30, 0, 0, 0, 0, time.UTC), time.Date(1926, time.November, 25, 0, 0, 0, 0, time.UTC), time.Date(1989, time.January, 8, 0, 0, 0, 0, time.UTC), time.Date(2019, time.April, 1, 0, 0, 0, 0, time.UTC), } // japaneseEraNames list the Japanese era name for the Japanese emperor // reign calendar. japaneseEraNames = []string{"\u660E\u6CBB", "\u5927\u6B63", "\u662D\u548C", "\u5E73\u6210", "\u4EE4\u548C"} // japaneseEraYear list the Japanese era name symbols. japaneseEraSymbols = []string{"M", "T", "S", "H", "R"} // monthNamesAfrikaans list the month names in the Afrikaans. monthNamesAfrikaans = []string{"Januarie", "Februarie", "Maart", "April", "Mei", "Junie", "Julie", "Augustus", "September", "Oktober", "November", "Desember"} // monthNamesAfrikaansAbbr lists the month name abbreviations in the // Afrikaans. monthNamesAfrikaansAbbr = []string{"Jan.", "Feb.", "Maa.", "Apr.", "Mei", "Jun.", "Jul.", "Aug.", "Sep.", "Okt.", "Nov.", "Des."} // monthNamesAlbanian list the month names in the Albanian. monthNamesAlbanian = []string{"janar", "shkurt", "mars", "prill", "maj", "qershor", "korrik", "gusht", "shtator", "tetor", "nëntor", "dhjetor"} // monthNamesAlbanianAbbr lists the month name abbreviations in the // Albanian. monthNamesAlbanianAbbr = []string{"jan", "shk", "mar", "pri", "maj", "qer", "krr", "gush", "sht", "tet", "nën", "dhj"} // monthNamesAlsatian list the month names in the Alsatian. monthNamesAlsatian = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "Auguscht", "Septämber", "Oktoober", "Novämber", "Dezämber"} // monthNamesAlsatianAbbr lists the month name abbreviations in the // Alsatian France. monthNamesAlsatianAbbr = []string{"Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"} // monthNamesAlsatianFrance list the month names in the Alsatian. monthNamesAlsatianFrance = []string{"Jänner", "Feverje", "März", "Àpril", "Mai", "Jüni", "Jüli", "Augscht", "September", "Oktower", "Nowember", "Dezember"} // monthNamesAlsatianFranceAbbr lists the month name abbreviations in the // Alsatian France. monthNamesAlsatianFranceAbbr = []string{"Jän.", "Fev.", "März", "Apr.", "Mai", "Jüni", "Jüli", "Aug.", "Sept.", "Okt.", "Now.", "Dez."} // monthNamesAmharic list the month names in the Amharic. monthNamesAmharic = []string{ "\u1303\u1295\u12E9\u12C8\u122A", "\u134C\u1265\u1229\u12C8\u122A", "\u121B\u122D\u127D", "\u12A4\u1355\u122A\u120D", "\u121C\u12ED", "\u1301\u1295", "\u1301\u120B\u12ED", "\u12A6\u1308\u1235\u1275", "\u1234\u1355\u1274\u121D\u1260\u122D", "\u12A6\u12AD\u1276\u1260\u122D", "\u1296\u126C\u121D\u1260\u122D", "\u12F2\u1234\u121D\u1260\u122D", } // monthNamesAmharicAbbr lists the month name abbreviations in the Amharic. monthNamesAmharicAbbr = []string{ "\u1303\u1295\u12E9", "\u134C\u1265\u1229", "\u121B\u122D\u127D", "\u12A4\u1355\u122A", "\u121C\u12ED", "\u1301\u1295", "\u1301\u120B\u12ED", "\u12A6\u1308\u1235", "\u1234\u1355\u1274", "\u12A6\u12AD\u1276", "\u1296\u126C\u121D", "\u12F2\u1234\u121D", } // monthNamesArabic list the month names in the Arabic. monthNamesArabic = []string{ "\u064A\u0646\u0627\u064A\u0631", "\u0641\u0628\u0631\u0627\u064A\u0631", "\u0645\u0627\u0631\u0633", "\u0623\u0628\u0631\u064A\u0644", "\u0645\u0627\u064A\u0648", "\u064A\u0648\u0646\u064A\u0648", "\u064A\u0648\u0644\u064A\u0648", "\u0623\u063A\u0633\u0637\u0633", "\u0633\u0628\u062A\u0645\u0628\u0631", "\u0623\u0643\u062A\u0648\u0628\u0631", "\u0646\u0648\u0641\u0645\u0628\u0631", "\u062F\u064A\u0633\u0645\u0628\u0631", } // monthNamesArabicIraq list the month names in the Arabic Iraq. monthNamesArabicIraq = []string{ "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A", "\u0634\u0628\u0627\u0637", "\u0622\u0630\u0627\u0631", "\u0646\u064A\u0633\u0627\u0646", "\u0623\u064A\u0627\u0631", "\u062D\u0632\u064A\u0631\u0627\u0646", "\u062A\u0645\u0648\u0632", "\u0622\u0628", "\u0623\u064A\u0644\u0648\u0644", "\u062A\u0634\u0631\u064A\u0646 \u0627\u0644\u0623\u0648\u0644", "\u062A\u0634\u0631\u064A\u0646 \u0627\u0644\u062B\u0627\u0646\u064A", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644", } // monthNamesArmenian list the month names in the Armenian. monthNamesArmenian = []string{ "\u0540\u0578\u0582\u0576\u057E\u0561\u0580", "\u0553\u0565\u057F\u0580\u057E\u0561\u0580", "\u0544\u0561\u0580\u057F", "\u0531\u057A\u0580\u056B\u056C", "\u0544\u0561\u0575\u056B\u057D", "\u0540\u0578\u0582\u0576\u056B\u057D", "\u0540\u0578\u0582\u056C\u056B\u057D", "\u0555\u0563\u0578\u057D\u057F\u0578\u057D", "\u054D\u0565\u057A\u057F\u0565\u0574\u0562\u0565\u0580", "\u0540\u0578\u056F\u057F\u0565\u0574\u0562\u0565\u0580", "\u0546\u0578\u0575\u0565\u0574\u0562\u0565\u0580", "\u0534\u0565\u056F\u057F\u0565\u0574\u0562\u0565\u0580", } // monthNamesArmenianAbbr lists the month name abbreviations in the // Armenian. monthNamesArmenianAbbr = []string{ "\u0540\u0576\u057E", "\u0553\u057F\u057E", "\u0544\u0580\u057F", "\u0531\u057A\u0580", "\u0544\u0575\u057D", "\u0540\u0576\u057D", "\u0540\u056C\u057D", "\u0555\u0563\u057D", "\u054D\u057A\u057F", "\u0540\u056F\u057F", "\u0546\u0575\u0574", "\u0534\u056F\u057F", } // monthNamesAssamese list the month names in the Assamese. monthNamesAssamese = []string{ "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0", "\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09F1\u09BE\u09F0\u09C0", "\u09AE\u09BE\u09B0\u09CD\u099A", "\u098F\u09AA\u09CD\u09B0\u09BF\u09B2", "\u09AE\u09C7", "\u099C\u09C1\u09A8", "\u099C\u09C1\u09B2\u09BE\u0987", "\u0986\u0997\u09B7\u09CD\u099F", "\u099A\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09F0", "\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09F0", "\u09A8\u09AC\u09C7\u09AE\u09CD\u09AC\u09F0", "\u09A1\u09BF\u099A\u09C7\u09AE\u09CD\u09AC\u09F0", } // monthNamesAssameseAbbr lists the month name abbreviations in the // Assamese. monthNamesAssameseAbbr = []string{ "\u099C\u09BE\u09A8\u09C1", "\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1", "\u09AE\u09BE\u09B0\u09CD\u099A", "\u098F\u09AA\u09CD\u09B0\u09BF\u09B2", "\u09AE\u09C7", "\u099C\u09C1\u09A8", "\u099C\u09C1\u09B2\u09BE\u0987", "\u0986\u0997\u09B7\u09CD\u099F", "\u099A\u09C7\u09AA\u09CD\u099F\u09C7", "\u0985\u0995\u09CD\u099F\u09CB", "\u09A8\u09AC\u09C7", "\u09A1\u09BF\u099A\u09C7", } // monthNamesAzerbaijaniCyrillic list the month names in the Azerbaijani // (Cyrillic). monthNamesAzerbaijaniCyrillic = []string{ "j\u0430\u043D\u0432\u0430\u0440", "\u0444\u0435\u0432\u0440\u0430\u043B", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0435\u043B", "\u043C\u0430\u0458", "\u0438\u0458\u0443\u043D", "\u0438\u0458\u0443\u043B", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043D\u0442\u0458\u0430\u0431\u0440", "\u043E\u043A\u0442\u0458\u0430\u0431\u0440", "\u043D\u043E\u0458\u0430\u0431\u0440", "\u0434\u0435\u043A\u0430\u0431\u0440", } // monthNamesAzerbaijaniCyrillicAbbr lists the month name abbreviations in // the Azerbaijani (Cyrillic). monthNamesAzerbaijaniCyrillicAbbr = []string{ "\u0408\u0430\u043D", "\u0424\u0435\u0432", "\u041C\u0430\u0440", "\u0410\u043F\u0440", "\u041C\u0430\u0458", "\u0418\u0458\u0443\u043D", "\u0418\u0458\u0443\u043B", "\u0410\u0432\u0433", "\u0421\u0435\u043D", "\u041E\u043A\u0442", "\u041D\u043E\u044F", "\u0414\u0435\u043A", } // monthNamesAzerbaijani list the month names in the Azerbaijani. monthNamesAzerbaijani = []string{"yanvar", "fevral", "mart", "aprel", "may", "iyun", "iyul", "avgust", "sentyabr", "oktyabr", "noyabr", "dekabr"} // monthNamesAzerbaijaniAbbr lists the month name abbreviations in the // Azerbaijani. monthNamesAzerbaijaniAbbr = []string{"yan", "fev", "mar", "apr", "may", "iyn", "iyl", "avq", "sen", "okt", "noy", "dek"} // monthNamesAustria list the month names in the Austrian. monthNamesAustria = []string{"Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"} // monthNamesAustriaAbbr list the month name abbreviations in the Austrian. monthNamesAustriaAbbr = []string{"Jän", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"} // monthNamesBangla list the month names in the Bangla. monthNamesBangla = []string{ "\u099C\u09BE\u09A8\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0", "\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0", "\u09AE\u09BE\u09B0\u09CD\u099A", "\u098F\u09AA\u09CD\u09B0\u09BF\u09B2", "\u09AE\u09C7", "\u099C\u09C1\u09A8", "\u099C\u09C1\u09B2\u09BE\u0987", "\u0986\u0997\u09B8\u09CD\u099F", "\u09B8\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09B0", "\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09B0", "\u09A8\u09AD\u09C7\u09AE\u09CD\u09AC\u09B0", "\u09A1\u09BF\u09B8\u09C7\u09AE\u09CD\u09AC\u09B0", } // monthNamesBashkir list the month names in the Bashkir. monthNamesBashkir = []string{ "\u0493\u0438\u043D\u0443\u0430\u0440", "\u0444\u0435\u0432\u0440\u0430\u043B\u044C", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0435\u043B\u044C", "\u043C\u0430\u0439", "\u0438\u044E\u043D\u044C", "\u0438\u044E\u043B\u044C", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C", "\u043E\u043A\u0442\u044F\u0431\u0440\u044C", "\u043D\u043E\u044F\u0431\u0440\u044C", "\u0434\u0435\u043A\u0430\u0431\u0440\u044C", } // monthNamesBashkirAbbr lists the month name abbreviations in the Bashkir. monthNamesBashkirAbbr = []string{ "\u0493\u0438\u043D", "\u0444\u0435\u0432", "\u043C\u0430\u0440", "\u0430\u043F\u0440", "\u043C\u0430\u0439", "\u0438\u044E\u043D", "\u0438\u044E\u043B", "\u0430\u0432\u0433", "\u0441\u0435\u043D", "\u043E\u043A\u0442", "\u043D\u043E\u044F", "\u0434\u0435\u043A", } // monthNamesBasque list the month names in the Basque. monthNamesBasque = []string{"urtarrila", "otsaila", "martxoa", "apirila", "maiatza", "ekaina", "uztaila", "abuztua", "iraila", "urria", "azaroa", "abendua"} // monthNamesBasqueAbbr lists the month name abbreviations in the Basque. monthNamesBasqueAbbr = []string{"urt.", "ots.", "mar.", "api.", "mai.", "eka.", "uzt.", "abu.", "ira.", "urr.", "aza.", "abe."} // monthNamesBelarusian list the month names in the Belarusian. monthNamesBelarusian = []string{ "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C", "\u043B\u044E\u0442\u044B", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A", "\u043A\u0440\u0430\u0441\u0430\u0432\u0456\u043A", "\u043C\u0430\u0439", "\u0447\u044D\u0440\u0432\u0435\u043D\u044C", "\u043B\u0456\u043F\u0435\u043D\u044C", "\u0436\u043D\u0456\u0432\u0435\u043D\u044C", "\u0432\u0435\u0440\u0430\u0441\u0435\u043D\u044C", "\u043A\u0430\u0441\u0442\u0440\u044B\u0447\u043D\u0456\u043A", "\u043B\u0456\u0441\u0442\u0430\u043F\u0430\u0434", "\u0441\u043D\u0435\u0436\u0430\u043D\u044C", } // monthNamesBelarusianAbbr lists the month name abbreviations in the // Belarusian. monthNamesBelarusianAbbr = []string{ "\u0441\u0442\u0443\u0434\u0437", "\u043B\u044E\u0442", "\u0441\u0430\u043A", "\u043A\u0440\u0430\u0441", "\u043C\u0430\u0439", "\u0447\u044D\u0440\u0432", "\u043B\u0456\u043F", "\u0436\u043D", "\u0432\u0435\u0440", "\u043A\u0430\u0441\u0442\u0440", "\u043B\u0456\u0441\u0442", "\u0441\u043D\u0435\u0436", } // monthNamesBosnianCyrillic list the month names in the Bosnian (Cyrillic). monthNamesBosnianCyrillic = []string{ "\u0458\u0430\u043D\u0443\u0430\u0440", "\u0444\u0435\u0431\u0440\u0443\u0430\u0440", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0438\u043B", "\u043C\u0430\u0458", "\u0458\u0443\u043D", "\u0458\u0443\u043B", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440", "\u043E\u043A\u0442\u043E\u0431\u0430\u0440", "\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440", "\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440", } // monthNamesBosnianCyrillicAbbr lists the month name abbreviations in the // Bosnian (Cyrillic). monthNamesBosnianCyrillicAbbr = []string{ "\u0458\u0430\u043D", "\u0444\u0435\u0431", "\u043C\u0430\u0440", "\u0430\u043F\u0440", "\u043C\u0430\u0458", "\u0458\u0443\u043D", "\u0458\u0443\u043B", "\u0430\u0432\u0433", "\u0441\u0435\u043F", "\u043E\u043A\u0442", "\u043D\u043E\u0432", "\u0434\u0435\u0446", } // monthNamesBosnian list the month names in the Bosnian. monthNamesBosnian = []string{"januar", "februar", "mart", "april", "maj", "juni", "juli", "august", "septembar", "oktobar", "novembar", "decembar"} // monthNamesBosnianAbbr lists the month name abbreviations in the Bosnian. monthNamesBosnianAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"} // monthNamesBreton list the month names in the Breton. monthNamesBreton = []string{"Genver", "C\u02bchwevrer", "Meurzh", "Ebrel", "Mae", "Mezheven", "Gouere", "Eost", "Gwengolo", "Here", "Du", "Kerzu"} // monthNamesBretonAbbr lists the month name abbreviations in the Breton. monthNamesBretonAbbr = []string{"Gen.", "C\u02bchwe.", "Meur.", "Ebr.", "Mae", "Mezh.", "Goue.", "Eost", "Gwen.", "Here", "Du", "Kzu."} // monthNamesBulgarian list the month names in the Bulgarian. monthNamesBulgarian = []string{ "\u044F\u043D\u0443\u0430\u0440\u0438", "\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0438\u043B", "\u043C\u0430\u0439", "\u044E\u043D\u0438", "\u044E\u043B\u0438", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043F\u0442\u0435\u043C\u0432\u0440\u0438", "\u043E\u043A\u0442\u043E\u043C\u0432\u0440\u0438", "\u043D\u043E\u0435\u043C\u0432\u0440\u0438", "\u0434\u0435\u043A\u0435\u043C\u0432\u0440\u0438", } // monthNamesBurmese list the month names in the Burmese. monthNamesBurmese = []string{ "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E", "\u1016\u1031\u1016\u1031\u102C\u103A\u101D\u102B\u101B\u102E", "\u1019\u1010\u103A", "\u1027\u1015\u103C\u102E", "\u1019\u1031", "\u1007\u103D\u1014\u103A", "\u1007\u1030\u101C\u102D\u102F\u1004\u103A", "\u1029\u1002\u102F\u1010\u103A", "\u1005\u1000\u103A\u1010\u1004\u103A\u1018\u102C", "\u1021\u1031\u102C\u1000\u103A\u1010\u102D\u102F\u1018\u102C", "\u1014\u102D\u102F\u101D\u1004\u103A\u1018\u102C", "\u1012\u102E\u1007\u1004\u103A\u1018\u102C", } // monthNamesBurmeseAbbr lists the month name abbreviations in the Burmese. monthNamesBurmeseAbbr = []string{ "\u1007\u1014\u103A", "\u1016\u1031", "\u1019\u1010\u103A", "\u1027", "\u1019\u1031", "\u1007\u103D\u1014\u103A", "\u1007\u1030", "\u1029", "\u1005\u1000\u103A", "\u1021\u1031\u102C\u1000\u103A", "\u1014\u102D\u102F", "\u1012\u102E", } // monthNamesCaribbean list the month names in the Caribbean. monthNamesCaribbean = []string{"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"} // monthNamesCaribbeanAbbr lists the month name abbreviations in the // Caribbean. monthNamesCaribbeanAbbr = []string{"Janv.", "Févr.", "Mars", "Avr.", "Mai", "Juin", "Juil.", "Août", "Sept.", "Oct.", "Nov.", "Déc."} // monthNamesCentralKurdish list the month names in the Central Kurdish. monthNamesCentralKurdish = []string{ "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645", "\u0634\u0648\u0628\u0627\u062A", "\u0626\u0627\u0632\u0627\u0631", "\u0646\u06CC\u0633\u0627\u0646", "\u0626\u0627\u06CC\u0627\u0631", "\u062D\u0648\u0632\u06D5\u06CC\u0631\u0627\u0646", "\u062A\u06D5\u0645\u0648\u0648\u0632", "\u0626\u0627\u0628", "\u0626\u06D5\u06CC\u0644\u0648\u0648\u0644", "\u062A\u0634\u0631\u06CC\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645", "\u062A\u0634\u0631\u06CC\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645", "\u06A9\u0627\u0646\u0648\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645", } // monthNamesCherokee list the month names in the Cherokee. monthNamesCherokee = []string{ "\u13A4\u13C3\u13B8\u13D4\u13C5", "\u13A7\u13A6\u13B5", "\u13A0\u13C5\u13F1", "\u13DD\u13EC\u13C2", "\u13A0\u13C2\u13CD\u13AC\u13D8", "\u13D5\u13AD\u13B7\u13F1", "\u13AB\u13F0\u13C9\u13C2", "\u13A6\u13B6\u13C2", "\u13DA\u13B5\u13CD\u13D7", "\u13DA\u13C2\u13C5\u13D7", "\u13C5\u13D3\u13D5\u13C6", "\u13A4\u13CD\u13A9\u13F1", } // monthNamesCherokeeAbbr lists the month name abbreviations in the // Cherokee. monthNamesCherokeeAbbr = []string{ "\u13A4\u13C3\u13B8", "\u13A7\u13A6\u13B5", "\u13A0\u13C5\u13F1", "\u13DD\u13EC\u13C2", "\u13A0\u13C2\u13CD", "\u13D5\u13AD\u13B7", "\u13AB\u13F0\u13C9", "\u13A6\u13B6\u13C2", "\u13DA\u13B5\u13CD", "\u13DA\u13C2\u13C5", "\u13C5\u13D3\u13D5", "\u13A4\u13CD\u13A9", } // monthNamesChinese list the month names in the Chinese. monthNamesChinese = []string{"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"} // monthNamesChineseAbbr lists the month name abbreviations in the Chinese. monthNamesChineseAbbr = []string{"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"} // monthNamesChineseNum list the month number and character abbreviation in // the Chinese. monthNamesChineseNum = []string{"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"} // monthNamesCorsican list the month names in the Corsican. monthNamesCorsican = []string{"ghjennaghju", "ferraghju", "marzu", "aprile", "maghju", "ghjunghju", "lugliu", "aostu", "settembre", "ottobre", "nuvembre", "dicembre"} // monthNamesCorsican lists the month name abbreviations in the Corsican. monthNamesCorsicanAbbr = []string{"ghje", "ferr", "marz", "apri", "magh", "ghju", "lugl", "aost", "sett", "otto", "nuve", "dice"} // monthNamesCroatian list the month names in the Croatian. monthNamesCroatian = []string{"siječanj", "veljača", "ožujak", "travanj", "svibanj", "lipanj", "srpanj", "kolovoz", "rujan", "listopad", "studeni", "prosinac"} // monthNamesCroatian lists the month name abbreviations in the Croatian. monthNamesCroatianAbbr = []string{"sij", "vlj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro"} // monthNamesCzech list the month names in the Czech. monthNamesCzech = []string{"leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec"} // monthNamesCzech lists the month name abbreviations in the Czech. monthNamesCzechAbbr = []string{"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"} // monthNamesDanish list the month names in the Danish. monthNamesDanish = []string{"januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december"} // monthNamesDanish lists the month name abbreviations in the Danish. monthNamesDanishAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"} // monthNamesDari list the month names in the Dari. monthNamesDari = []string{ "\u062C\u0646\u0648\u0631\u06CC", "\u0641\u0628\u0631\u0648\u0631\u06CC", "\u0645\u0627\u0631\u0686", "\u0627\u067E\u0631\u06CC\u0644", "\u0645\u06CC", "\u062C\u0648\u0646", "\u062C\u0648\u0644\u0627\u06CC", "\u0627\u06AF\u0633\u062A", "\u0633\u067E\u062A\u0645\u0628\u0631", "\u0627\u06A9\u062A\u0648\u0628\u0631", "\u0646\u0648\u0645\u0628\u0631", "\u062F\u0633\u0645\u0628\u0631", } // monthNamesDariAbbr lists the month name abbreviations in the Dari. monthNamesDariAbbr = []string{ "\u062C\u062F\u06CC", "\u062F\u0644\u0648", "\u062D\u0648\u062A", "\u062D\u0645\u0644", "\u062B\u0648\u0631", "\u062C\u0648\u0632\u0627", "\u0633\u0631\u0637\u0627\u0646", "\u0627\u0633\u062F", "\u0633\u0646\u0628\u0644\u0647", "\u0645\u06CC\u0632\u0627\u0646", "\u0639\u0642\u0631\u0628", "\u0642\u0648\u0633", } // monthNamesDivehi lists the month names in the Divehi. monthNamesDivehi = []string{ "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9", "\u0789\u07A7\u0783\u0797\u07B0", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0", "\u0789\u07AC\u0787\u07A8", "\u0796\u07AB\u0782\u07B0", "\u0796\u07AA\u078D\u07A6\u0787\u07A8", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783", } // monthNamesDutch list the month names in the Dutch. monthNamesDutch = []string{"januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"} // monthNamesDutch lists the month name abbreviations in the Dutch. monthNamesDutchAbbr = []string{"jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"} // monthNamesDzongkha list the month names in the Dzongkha. monthNamesDzongkha = []string{ "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F44\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B", } // monthNamesDzongkhaAbbr lists the month name abbreviations in the // Dzongkha. monthNamesDzongkhaAbbr = []string{ "\u0F5F\u0FB3\u0F0B\u0F21", "\u0F5F\u0FB3\u0F0B\u0F22", "\u0F5F\u0FB3\u0F0B\u0F23", "\u0F5F\u0FB3\u0F0B\u0F24", "\u0F5F\u0FB3\u0F0B\u0F25", "\u0F5F\u0FB3\u0F0B\u0F26", "\u0F5F\u0FB3\u0F0B\u0F27", "\u0F5F\u0FB3\u0F0B\u0F28", "\u0F5F\u0FB3\u0F0B\u0F29", "\u0F5F\u0FB3\u0F0B\u0F21\u0F20", "\u0F5F\u0FB3\u0F0B\u0F21\u0F21", "\u0F5F\u0FB3\u0F0B\u0F21\u0F22", } // monthNamesEstonian list the month names in the Estonian. monthNamesEstonian = []string{"jaanuar", "veebruar", "märts", "aprill", "mai", "juuni", "juuli", "august", "september", "oktoober", "november", "detsember"} // monthNamesEstonianAbbr lists the month name abbreviations in the // Estonian. monthNamesEstonianAbbr = []string{"jaan", "veebr", "märts", "apr", "mai", "juuni", "juuli", "aug", "sept", "okt", "nov", "dets"} // monthNamesFaroese list the month names in the Faroese. monthNamesFaroese = []string{"januar", "februar", "mars", "apríl", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember"} // monthsNameFaroeseAbbr lists the month name abbreviations in the Faroese. monthsNameFaroeseAbbr = []string{"jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des"} // monthNamesFilipino list the month names in the Filipino. monthNamesFilipino = []string{"Enero", "Pebrero", "Marso", "Abril", "Mayo", "Hunyo", "Hulyo", "Agosto", "Setyembre", "Oktubre", "Nobyembre", "Disyembre"} // monthNamesFilipinoAbbr lists the month name abbreviations in the // Filipino. monthNamesFilipinoAbbr = []string{"Ene", "Peb", "Mar", "Abr", "May", "Hun", "Hul", "Ago", "Set", "Okt", "Nob", "Dis"} // monthsNamesFinnish list the month names in the Finnish. monthNamesFinnish = []string{"Etammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"} // monthsNamesFinnishAbbr lists the month name abbreviations in the Finnish. monthNamesFinnishAbbr = []string{"tammi", "helmi", "maalis", "huhti", "touko", "kesä", "heinä", "elo", "syys", "loka", "marras", "joulu"} // monthNamesFrench list the month names in the French. monthNamesFrench = []string{"janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"} // monthNamesFrenchAbbr lists the month name abbreviations in the French. monthNamesFrenchAbbr = []string{"janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."} // monthNamesFrisian list the month names in the Frisian. monthNamesFrisian = []string{"Jannewaris", "Febrewaris", "Maart", "April", "Maaie", "Juny", "July", "Augustus", "Septimber", "Oktober", "Novimber", "Desimber"} // monthNamesFrisianAbbr lists the month name abbreviations in the Frisian. monthNamesFrisianAbbr = []string{"jan", "feb", "Mrt", "Apr", "maa", "Jun", "Jul", "Aug", "sep", "Okt", "Nov", "Des"} // monthNamesFulah list the month names in the Fulah. monthNamesFulah = []string{"siilo", "colte", "mbooy", "seeɗto", "duujal", "korse", "morso", "juko", "siilto", "yarkomaa", "jolal", "bowte"} // monthNamesFulahAbbr lists the month name abbreviations in the Fulah. monthNamesFulahAbbr = []string{"sii", "col", "mbo", "see", "duu", "kor", "mor", "juk", "slt", "yar", "jol", "bow"} // monthNamesGalician list the month names in the Galician. monthNamesGalician = []string{"Xaneiro", "Febreiro", "Marzo", "Abril", "Maio", "Xuño", "Xullo", "Agosto", "Setembro", "Outubro", "Novembro", "Decembro"} // monthNamesGalicianAbbr lists the month name abbreviations in the // Galician. monthNamesGalicianAbbr = []string{"Xan.", "Feb.", "Mar.", "Abr.", "Maio", "Xuño", "Xul.", "Ago.", "Set.", "Out.", "Nov.", "Dec."} // monthNamesGeorgian list the month names in the Georgian. monthNamesGeorgian = []string{ "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8", "\u10D7\u10D4\u10D1\u10D4\u10E0\u10D5\u10D0\u10DA\u10D8", "\u10DB\u10D0\u10E0\u10E2\u10D8", "\u10D0\u10DE\u10E0\u10D8\u10DA\u10D8", "\u10DB\u10D0\u10D8\u10E1\u10D8", "\u10D8\u10D5\u10DC\u10D8\u10E1\u10D8", "\u10D8\u10D5\u10DA\u10D8\u10E1\u10D8", "\u10D0\u10D2\u10D5\u10D8\u10E1\u10E2\u10DD", "\u10E1\u10D4\u10E5\u10E2\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8", "\u10DD\u10E5\u10E2\u10DD\u10DB\u10D1\u10D4\u10E0\u10D8", "\u10DC\u10DD\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8", "\u10D3\u10D4\u10D9\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8", } // monthNamesGeorgianAbbr lists the month name abbreviations in the // Georgian. monthNamesGeorgianAbbr = []string{ "\u10D8\u10D0\u10DC", "\u10D7\u10D4\u10D1", "\u10DB\u10D0\u10E0", "\u10D0\u10DE\u10E0", "\u10DB\u10D0\u10D8", "\u10D8\u10D5\u10DC", "\u10D8\u10D5\u10DA", "\u10D0\u10D2\u10D5", "\u10E1\u10D4\u10E5", "\u10DD\u10E5\u10E2", "\u10DC\u10DD\u10D4", "\u10D3\u10D4\u10D9", } // monthNamesGerman list the month names in the German. monthNamesGerman = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"} // monthNamesGermanAbbr list the month abbreviations in the German. monthNamesGermanAbbr = []string{"Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"} // monthNamesGreek list the month names in the Greek. monthNamesGreek = []string{ "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2", "\u03A6\u03B5\u03B2\u03C1\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2", "\u0391\u03C0\u03C1\u03AF\u03BB\u03B9\u03BF\u03C2", "\u039C\u03AC\u03B9\u03BF\u03C2", "\u0399\u03BF\u03CD\u03BD\u03B9\u03BF\u03C2", "\u0399\u03BF\u03CD\u03BB\u03B9\u03BF\u03C2", "\u0391\u03CD\u03B3\u03BF\u03C5\u03C3\u03C4\u03BF\u03C2", "\u03A3\u03B5\u03C0\u03C4\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2", "\u039F\u03BA\u03C4\u03CE\u03B2\u03C1\u03B9\u03BF\u03C2", "\u039D\u03BF\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2", "\u0394\u03B5\u03BA\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2", } // monthNamesGreekAbbr list the month abbreviations in the Greek. monthNamesGreekAbbr = []string{ "\u0399\u03B1\u03BD", "\u03A6\u03B5\u03B2", "\u039C\u03B1\u03C1", "\u0391\u03C0\u03C1", "\u039C\u03B1\u03CA", "\u0399\u03BF\u03C5\u03BD", "\u0399\u03BF\u03C5\u03BB", "\u0391\u03C5\u03B3", "\u03A3\u03B5\u03C0", "\u039F\u03BA\u03C4", "\u039D\u03BF\u03B5", "\u0394\u03B5\u03BA", } // monthNamesGreenlandic list the month names in the Greenlandic. monthNamesGreenlandic = []string{"januaari", "februaari", "marsi", "apriili", "maaji", "juuni", "juuli", "aggusti", "septembari", "oktobari", "novembari", "decembari"} // monthNamesGreenlandicAbbr list the month abbreviations in the // Greenlandic. monthNamesGreenlandicAbbr = []string{"jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "dec"} // monthNamesGuarani list the month names in the Guarani. monthNamesGuarani = []string{"jasyte\u0129", "jasykõi", "jasyapy", "jasyrundy", "jasypo", "jasypote\u0129", "jasypokõi", "jasypoapy", "jasyporundy", "jasypa", "jasypate\u0129", "jasypakõi"} // monthNamesGuaraniAbbr list the month abbreviations in the Guarani. monthNamesGuaraniAbbr = []string{"jteĩ", "jkõi", "japy", "jrun", "jpo", "jpot", "jpok", "jpoa", "jpor", "jpa", "jpat", "jpak"} // monthNamesGujarati list the month names in the Gujarati. monthNamesGujarati = []string{ "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0", "\u0AAB\u0AC7\u0AAC\u0ACD\u0AB0\u0AC1\u0A86\u0AB0\u0AC0", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A", "\u0A8F\u0AAA\u0ACD\u0AB0\u0ABF\u0AB2", "\u0AAE\u0AC7", "\u0A9C\u0AC2\u0AA8", "\u0A9C\u0AC1\u0AB2\u0ABE\u0A88", "\u0A91\u0A97\u0AB8\u0ACD\u0A9F", "\u0AB8\u0AAA\u0ACD\u0A9F\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0", "\u0A91\u0A95\u0ACD\u0A9F\u0ACB\u0AAC\u0AB0", "\u0AA8\u0AB5\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0", "\u0AA1\u0ABF\u0AB8\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0", } // monthNamesGujaratiAbbr list the month abbreviations in the Gujarati. monthNamesGujaratiAbbr = []string{ "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1", "\u0AAB\u0AC7\u0AAC\u0ACD\u0AB0\u0AC1", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A", "\u0A8F\u0AAA\u0ACD\u0AB0\u0ABF\u0AB2", "\u0AAE\u0AC7", "\u0A9C\u0AC2\u0AA8", "\u0A9C\u0AC1\u0AB2\u0ABE\u0A88", "\u0A91\u0A97", "\u0AB8\u0AAA\u0ACD\u0A9F\u0AC7", "\u0A91\u0A95\u0ACD\u0A9F\u0ACB", "\u0AA8\u0AB5\u0AC7", "\u0AA1\u0ABF\u0AB8\u0AC7", } // monthNamesHausa list the month names in the Hausa. monthNamesHausa = []string{"Janairu", "Fabrairu", "Maris", "Afirilu", "Mayu", "Yuni", "Yuli", "Agusta", "Satumba", "Oktoba", "Nuwamba", "Disamba"} // monthNamesHawaiian list the month names in the Hawaiian. monthNamesHawaiian = []string{"Ianuali", "Pepeluali", "Malaki", "\u02bbApelila", "Mei", "Iune", "Iulai", "\u02bbAukake", "Kepakemapa", "\u02bbOkakopa", "Nowemapa", "Kekemapa"} // monthNamesHawaiianAbbr list the month name abbreviations in the // Hawaiiann. monthNamesHawaiianAbbr = []string{"Ian.", "Pep.", "Mal.", "\u02bbAp.", "Mei", "Iun.", "Iul.", "\u02bbAu.", "Kep.", "\u02bbOk.", "Now.", "Kek."} // monthNamesHebrew list the month names in the Hebrew. monthNamesHebrew = []string{ "\u05D9\u05E0\u05D5\u05D0\u05E8", "\u05E4\u05D1\u05E8\u05D5\u05D0\u05E8", "\u05DE\u05E8\u05E5", "\u05D0\u05E4\u05E8\u05D9\u05DC", "\u05DE\u05D0\u05D9", "\u05D9\u05D5\u05E0\u05D9", "\u05D9\u05D5\u05DC\u05D9", "\u05D0\u05D5\u05D2\u05D5\u05E1\u05D8", "\u05E1\u05E4\u05D8\u05DE\u05D1\u05E8", "\u05D0\u05D5\u05E7\u05D8\u05D5\u05D1\u05E8", "\u05E0\u05D5\u05D1\u05DE\u05D1\u05E8", "\u05D3\u05E6\u05DE\u05D1\u05E8", } // monthNamesHindi list the month names in the Hindi. monthNamesHindi = []string{ "\u091C\u0928\u0935\u0930\u0940", "\u092B\u0930\u0935\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u0905\u092A\u094D\u0930\u0948\u0932", "\u092E\u0908", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u093E\u0908", "\u0905\u0917\u0938\u094D\u0924", "\u0938\u093F\u0924\u092E\u094D\u092C\u0930", "\u0905\u0915\u094D\u0924\u0942\u092C\u0930", "\u0928\u0935\u092E\u094D\u092C\u0930", "\u0926\u093F\u0938\u092E\u094D\u092C\u0930", } // monthNamesHungarian list the month names in the Hungarian. monthNamesHungarian = []string{"január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december"} // monthNamesHungarianAbbr list the month name abbreviations in the // Hungarian. monthNamesHungarianAbbr = []string{"jan.", "febr.", "márc.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec."} // monthNamesIcelandic list the month names in the Icelandic. monthNamesIcelandic = []string{"janúar", "febrúar", "mars", "apríl", "maí", "júní", "júlí", "ágúst", "september", "október", "nóvember", "desember"} // monthNamesIcelandicAbbr list the month name abbreviations in the // Icelandic. monthNamesIcelandicAbbr = []string{"jan.", "feb.", "mar.", "apr.", "maí", "jún.", "júl.", "ágú.", "sep.", "okt.", "nóv.", "des."} // monthNamesIgbo list the month names in the Igbo. monthNamesIgbo = []string{"Jenụwarị", "Febụwarị", "Machị", "Eprelu", "Mey", "Juun", "Julaị", "Ọgọst", "Septemba", "Ọcktọba", "Nọvemba", "Disemba"} // monthNamesIgboAbbr list the month name abbreviations in the Igbo. monthNamesIgboAbbr = []string{"Jen", "Feb", "Mac", "Epr", "Mey", "Jun", "Jul", "Ọgọ", "Sep", "Ọkt", "Nọv", "Dis"} // monthNamesIndonesian list the month names in the Indonesian. monthNamesIndonesian = []string{"Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"} // monthNamesIndonesianAbbr list the month name abbreviations in the // Indonesian. monthNamesIndonesianAbbr = []string{"Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"} // monthNamesInuktitut list the month names in the Inuktitut. monthNamesInuktitut = []string{"Jaannuari", "Viivvuari", "Maatsi", "Iipuri", "Mai", "Juuni", "Julai", "Aaggiisi", "Sitipiri", "Utupiri", "Nuvipiri", "Tisipiri"} // monthNamesInuktitutAbbr list the month name abbreviations in the // Inuktitut. monthNamesInuktitutAbbr = []string{"Jan", "Viv", "Mas", "Ipu", "Mai", "Jun", "Jul", "Agi", "Sii", "Uut", "Nuv", "Tis"} // monthNamesIrish list the month names in the Irish. monthNamesIrish = []string{"Eanáir", "Feabhra", "Márta", "Aibreán", "Bealtaine", "Meitheamh", "Iúil", "Lúnasa", "Meán Fómhair", "Deireadh Fómhair", "Samhain", "Nollaig"} // monthNamesIrishAbbr lists the month abbreviations in the Irish. monthNamesIrishAbbr = []string{"Ean", "Feabh", "Márta", "Aib", "Beal", "Meith", "Iúil", "Lún", "MFómh", "DFómh", "Samh", "Noll"} // monthNamesItalian list the month names in the Italian. monthNamesItalian = []string{"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"} // monthNamesItalianAbbr list the month name abbreviations in the Italian. monthNamesItalianAbbr = []string{"gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic"} // monthNamesKannada list the month names in the Kannada. monthNamesKannada = []string{ "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF", "\u0CAB\u0CC6\u0CAC\u0CCD\u0CB0\u0CB5\u0CB0\u0CBF", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD", "\u0C8F\u0C8F\u0CAA\u0CCD\u0CB0\u0CBF\u0CB2\u0CCD", "\u0CAE\u0CC7", "\u0C9C\u0CC2\u0CA8\u0CCD", "\u0C9C\u0CC1\u0CB2\u0CC8", "\u0C86\u0C97\u0CB8\u0CCD\u0C9F\u0CCD", "\u0CB8\u0CC6\u0CAA\u0CCD\u0C9F\u0C82\u0CAC\u0CB0\u0CCD", "\u0C85\u0C95\u0CCD\u0C9F\u0CCB\u0CAC\u0CB0\u0CCD", "\u0CA8\u0CB5\u0CC6\u0C82\u0CAC\u0CB0\u0CCD", "\u0CA1\u0CBF\u0CB8\u0CC6\u0C82\u0CAC\u0CB0\u0CCD", } // monthNamesKannadaAbbr lists the month abbreviations in the Kannada. monthNamesKannadaAbbr = []string{ "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF", "\u0CAB\u0CC6\u0CAC\u0CCD\u0CB0\u0CB5\u0CB0\u0CBF", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD", "\u0C8E\u0CAA\u0CCD\u0CB0\u0CBF\u0CB2\u0CCD", "\u0CAE\u0CC7", "\u0C9C\u0CC2\u0CA8\u0CCD", "\u0C9C\u0CC1\u0CB2\u0CC8", "\u0C86\u0C97\u0CB8\u0CCD\u0C9F\u0CCD", "\u0CB8\u0CC6\u0CAA\u0CCD\u0C9F\u0C82\u0CAC\u0CB0\u0CCD", "\u0C85\u0C95\u0CCD\u0C9F\u0CCB\u0CAC\u0CB0\u0CCD", "\u0CA8\u0CB5\u0CC6\u0C82\u0CAC\u0CB0\u0CCD", "\u0CA1\u0CBF\u0CB8\u0CC6\u0C82\u0CAC\u0CB0\u0CCD", } // monthNamesKashmiri list the month names in the Kashmiri. monthNamesKashmiri = []string{ "\u062C\u0646\u0624\u0631\u06CC", "\u0641\u0631\u0624\u0631\u06CC", "\u0645\u0627\u0631\u0655\u0686", "\u0627\u067E\u0631\u06CC\u0644", "\u0645\u06CC\u0654", "\u062C\u0648\u0657\u0646", "\u062C\u0648\u0657\u0644\u0627\u06CC\u06CC", "\u0627\u06AF\u0633\u062A", "\u0633\u062A\u0645\u0628\u0631", "\u0627\u06A9\u062A\u0648\u0657\u0628\u0631", "\u0646\u0648\u0645\u0628\u0631", "\u062F\u0633\u0645\u0628\u0631", } // monthNamesKazakh list the month names in the Kazakh. monthNamesKazakh = []string{ "\u049A\u0430\u04A3\u0442\u0430\u0440", "\u0410\u049B\u043F\u0430\u043D", "\u041D\u0430\u0443\u0440\u044B\u0437", "\u0421\u04D9\u0443\u0456\u0440", "\u041C\u0430\u043C\u044B\u0440", "\u041C\u0430\u0443\u0441\u044B\u043C", "\u0428\u0456\u043B\u0434\u0435", "\u0422\u0430\u043C\u044B\u0437", "\u049A\u044B\u0440\u043A\u04AF\u0439\u0435\u043A", "\u049A\u0430\u0437\u0430\u043D", "\u049A\u0430\u0440\u0430\u0448\u0430", "\u0416\u0435\u043B\u0442\u043E\u049B\u0441\u0430\u043D", } // monthNamesKazakhAbbr list the month name abbreviations in the Kazakh. monthNamesKazakhAbbr = []string{ "\u049B\u0430\u04A3", "\u0430\u049B\u043F", "\u043D\u0430\u0443", "\u0441\u04D9\u0443", "\u043C\u0430\u043C", "\u043C\u0430\u0443", "\u0448\u0456\u043B", "\u0442\u0430\u043C", "\u049B\u044B\u0440", "\u049B\u0430\u0437", "\u049B\u0430\u0440", "\u0436\u0435\u043B", } // monthNamesKhmer list the month names in the Khmer. monthNamesKhmer = []string{ "\u1798\u1780\u179A\u17B6", "\u1780\u17BB\u1798\u17D2\u1797\u17C8", "\u1798\u17B7\u1793\u17B6", "\u1798\u17C1\u179F\u17B6", "\u17A7\u179F\u1797\u17B6", "\u1798\u17B7\u1790\u17BB\u1793\u17B6", "\u1780\u1780\u17D2\u1780\u178A\u17B6", "\u179F\u17B8\u17A0\u17B6", "\u1780\u1789\u17D2\u1789\u17B6", "\u178F\u17BB\u179B\u17B6", "\u179C\u17B7\u1785\u17D2\u1786\u17B7\u1780\u17B6", "\u1792\u17D2\u1793\u17BC", } // monthNamesKhmerAbbr list the month name abbreviations in the Khmer. monthNamesKhmerAbbr = []string{ "\u17E1", "\u17E2", "\u17E3", "\u17E4", "\u17E5", "\u17E6", "\u17E7", "\u17E8", "\u17E9", "\u17E1\u17E0", "\u17E1\u17E1", "\u17E1\u17E2", "\u1798", "\u1780", "\u1798", "\u1798", "\u17A7", "\u1798", "\u1780", "\u179F", "\u1780", "\u178F", "\u179C", "\u1792", } // monthNamesKiche list the month names in the Kiche. monthNamesKiche = []string{"nab'e ik'", "ukab' ik'", "urox ik'", "ukaj ik'", "uro ik'", "uwaq ik'", "uwuq ik'", "uwajxaq ik'", "ub'elej ik'", "ulaj ik'", "ujulaj ik'", "ukab'laj ik'"} // monthNamesKicheAbbr list the month name abbreviations in the Kiche. monthNamesKicheAbbr = []string{"nab'e", "ukab'", "urox", "ukaj", "uro", "uwaq", "uwuq", "uwajxaq", "ub'elej", "ulaj", "ujulaj", "ukab'laj"} // monthNamesKinyarwanda list the month names in the Kinyarwanda. monthNamesKinyarwanda = []string{"Mutarama", "Gashyantare", "Werurwe", "Mata", "Gicuransi", "Kamena", "Nyakanga", "Kanama", "Nzeli", "Ukwakira", "Ugushyingo", "Ukuboza"} // monthNamesKinyarwandaAbbr list the month name abbreviations in the Kinyarwanda. monthNamesKinyarwandaAbbr = []string{"mut.", "gas.", "wer.", "mat.", "gic.", "kam.", "Nyak", "kan.", "nze.", "Ukwak", "Ugus", "Ukub"} // monthNamesKiswahili list the month names in the Kiswahili. monthNamesKiswahili = []string{"Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba"} // monthNamesKiswahiliAbbr list the month name abbreviations in the Kiswahili. monthNamesKiswahiliAbbr = []string{"Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des"} // monthNamesKonkani list the month names in the Konkani. monthNamesKonkani = []string{ "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940", "\u092B\u0947\u092C\u094D\u0930\u0941\u0935\u093E\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u090F\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u0948", "\u0911\u0917\u0938\u094D\u091F", "\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930", "\u0911\u0915\u094D\u091F\u094B\u092C\u0930", "\u0928\u094B\u0935\u0947\u092E\u094D\u092C\u0930", "\u0921\u093F\u0938\u0947\u0902\u092C\u0930", } // monthNamesKonkaniAbbr list the month name abbreviations in the Konkani. monthNamesKonkaniAbbr = []string{ "\u091C\u093E\u0928\u0947", "\u092B\u0947\u092C\u094D\u0930\u0941", "\u092E\u093E\u0930\u094D\u091A", "\u090F\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u0948", "\u0911\u0917.", "\u0938\u092A\u094D\u091F\u0947\u0902.", "\u0911\u0915\u094D\u091F\u094B.", "\u0928\u094B\u0935\u0947.", "\u0921\u093F\u0938\u0947\u0902", } // monthNamesKoreanAbbr lists out the month number plus 월 for the Korean language. monthNamesKoreanAbbr = []string{"1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"} // monthNamesKyrgyz list the month names in the Kyrgyz. monthNamesKyrgyz = []string{ "\u042F\u043D\u0432\u0430\u0440\u044C", "\u0424\u0435\u0432\u0440\u0430\u043B\u044C", "\u041C\u0430\u0440\u0442", "\u0410\u043F\u0440\u0435\u043B\u044C", "\u041C\u0430\u0439", "\u0418\u044E\u043D\u044C", "\u0418\u044E\u043B\u044C", "\u0410\u0432\u0433\u0443\u0441\u0442", "\u0421\u0435\u043D\u0442\u044F\u0431\u0440\u044C", "\u041E\u043A\u0442\u044F\u0431\u0440\u044C", "\u041D\u043E\u044F\u0431\u0440\u044C", "\u0414\u0435\u043A\u0430\u0431\u0440\u044C", } // monthNamesKyrgyzAbbr lists the month name abbreviations in the Kyrgyz. monthNamesKyrgyzAbbr = []string{ "\u042F\u043D\u0432", "\u0424\u0435\u0432", "\u041C\u0430\u0440", "\u0410\u043F\u0440", "\u041C\u0430\u0439", "\u0418\u044E\u043D", "\u0418\u044E\u043B", "\u0410\u0432\u0433", "\u0421\u0435\u043D", "\u041E\u043A\u0442", "\u041D\u043E\u044F", "\u0414\u0435\u043A", } // monthNamesLao list the month names in the Lao. monthNamesLao = []string{ "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99", "\u0E81\u0EB8\u0EA1\u0E9E\u0EB2", "\u0EA1\u0EB5\u0E99\u0EB2", "\u0EC0\u0EA1\u0EAA\u0EB2", "\u0E9E\u0EB6\u0E94\u0EAA\u0EB0\u0E9E\u0EB2", "\u0EA1\u0EB4\u0E96\u0EB8\u0E99\u0EB2", "\u0E81\u0ECD\u0EA5\u0EB0\u0E81\u0EBB\u0E94", "\u0EAA\u0EB4\u0E87\u0EAB\u0EB2", "\u0E81\u0EB1\u0E99\u0E8D\u0EB2", "\u0E95\u0EB8\u0EA5\u0EB2", "\u0E9E\u0EB0\u0E88\u0EB4\u0E81", "\u0E97\u0EB1\u0E99\u0EA7\u0EB2", } // monthNamesLaoAbbr lists the month name abbreviations in the Lao. monthNamesLaoAbbr = []string{ "\u0EA1.\u0E81.", "\u0E81.\u0E9E.", "\u0EA1.\u0E99.", "\u0EA1.\u0EAA.", "\u0E9E.\u0E9E.", "\u0EA1\u0EB4.\u0E96.", "\u0E81.\u0EA5.", "\u0EAA.\u0EAB.", "\u0E81.\u0E8D.", "\u0E95.\u0EA5.", "\u0E9E.\u0E88.", "\u0E97.\u0EA7.", } // monthNamesLatin list the month names in the Latin. monthNamesLatin = []string{"Ianuarius", "Februarius", "Martius", "Aprilis", "Maius", "Iunius", "Quintilis", "Sextilis", "September", "October", "November", "December"} // monthNamesLatinAbbr list the month name abbreviations in the Latin. monthNamesLatinAbbr = []string{"Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Quint", "Sext", "Sept", "Oct", "Nov", "Dec"} // monthNamesLatvian list the month names in the Latvian. monthNamesLatvian = []string{"janvāris", "februāris", "marts", "aprīlis", "maijs", "jūnijs", "jūlijs", "augusts", "septembris", "oktobris", "novembris", "decembris"} // monthNamesLatvianAbbr list the month name abbreviations in the Latvian. monthNamesLatvianAbbr = []string{"janv.", "febr.", "marts", "apr.", "maijs", "jūn.", "jūl.", "aug.", "sept.", "okt.", "nov.", "dec."} // monthNamesLithuanian list the month names in the Lithuanian. monthNamesLithuanian = []string{"sausis", "vasaris", "kovas", "balandis", "gegužė", "birželis", "liepa", "rugpjūtis", "rugsėjis", "spalis", "lapkritis", "gruodis"} // monthNamesLithuanianAbbr list the month name abbreviations in the Lithuanian. monthNamesLithuanianAbbr = []string{"saus.", "vas.", "kov.", "bal.", "geg.", "birž.", "liep.", "rugp.", "rugs.", "spal.", "lapkr.", "gruod."} // monthNamesLowerSorbian list the month names in the Lower Sorbian. monthNamesLowerSorbian = []string{"januar", "februar", "měrc", "apryl", "maj", "junij", "julij", "awgust", "september", "oktober", "nowember", "december"} // monthNamesLowerSorbianAbbr list the month name abbreviations in the LowerSorbian. monthNamesLowerSorbianAbbr = []string{"jan", "feb", "měr", "apr", "maj", "jun", "jul", "awg", "sep", "okt", "now", "dec"} // monthNamesLuxembourgish list the month names in the Lower Sorbian. monthNamesLuxembourgish = []string{"Januar", "Februar", "Mäerz", "Abrëll", "Mee", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"} // monthNamesLuxembourgishAbbr list the month name abbreviations in the Luxembourgish. monthNamesLuxembourgishAbbr = []string{"Jan", "Feb", "Mäe", "Abr", "Mee", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"} // monthNamesMacedonian list the month names in the Lower Sorbian. monthNamesMacedonian = []string{ "\u0458\u0430\u043D\u0443\u0430\u0440\u0438", "\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0438\u043B", "\u043C\u0430\u0458", "\u0458\u0443\u043D\u0438", "\u0458\u0443\u043B\u0438", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043F\u0442\u0435\u043C\u0432\u0440\u0438", "\u043E\u043A\u0442\u043E\u043C\u0432\u0440\u0438", "\u043D\u043E\u0435\u043C\u0432\u0440\u0438", "\u0434\u0435\u043A\u0435\u043C\u0432\u0440\u0438", } // monthNamesMacedonianAbbr list the month name abbreviations in the Macedonian. monthNamesMacedonianAbbr = []string{ "\u0458\u0430\u043D.", "\u0444\u0435\u0432.", "\u043C\u0430\u0440.", "\u0430\u043F\u0440.", "\u043C\u0430\u0458", "\u0458\u0443\u043D.", "\u0458\u0443\u043B.", "\u0430\u0432\u0433.", "\u0441\u0435\u043F\u0442.", "\u043E\u043A\u0442.", "\u043D\u043E\u0435\u043C.", "\u0434\u0435\u043A.", } // monthNamesMalay list the month names in the Malay. monthNamesMalay = []string{"Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember"} // monthNamesMalayAbbr list the month name abbreviations in the Malay. monthNamesMalayAbbr = []string{"Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis"} // monthNamesMalayalam list the month names in the Malayalam. monthNamesMalayalam = []string{ "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F", "\u0D2B\u0D46\u0D2C\u0D4D\u0D30\u0D41\u0D35\u0D30\u0D3F", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D", "\u0D0F\u0D2A\u0D4D\u0D30\u0D3F\u0D32\u0D4D\u200D", "\u0D2E\u0D47\u0D2F\u0D4D", "\u0D1C\u0D42\u0D7A", "\u0D1C\u0D42\u0D32\u0D48", "\u0D06\u0D17\u0D38\u0D4D\u0D31\u0D4D\u0D31\u0D4D", "\u0D38\u0D46\u0D2A\u0D4D\u200C\u0D31\u0D4D\u0D31\u0D02\u0D2C\u0D30\u0D4D\u200D", "\u0D12\u0D15\u0D4D\u200C\u0D1F\u0D4B\u0D2C\u0D30\u0D4D\u200D", "\u0D28\u0D35\u0D02\u0D2C\u0D30\u0D4D\u200D", "\u0D21\u0D3F\u0D38\u0D02\u0D2C\u0D30\u0D4D\u200D", } // monthNamesMalayalamAbbr list the month name abbreviations in the Malayalam. monthNamesMalayalamAbbr = []string{ "\u0D1C\u0D28\u0D41", "\u0D2B\u0D46\u0D2C\u0D4D\u0D30\u0D41", "\u0D2E\u0D3E\u0D7C", "\u0D0F\u0D2A\u0D4D\u0D30\u0D3F", "\u0D2E\u0D47\u0D2F\u0D4D", "\u0D1C\u0D42\u0D7A", "\u0D1C\u0D42\u0D32\u0D48", "\u0D13\u0D17", "\u0D38\u0D46\u0D2A\u0D4D\u0D31\u0D4D\u0D31\u0D02", "\u0D12\u0D15\u0D4D\u0D1F\u0D4B", "\u0D28\u0D35\u0D02", "\u0D21\u0D3F\u0D38\u0D02", } // monthNamesMaltese list the month names in the Maltese. monthNamesMaltese = []string{"Jannar", "Frar", "Marzu", "April", "Mejju", "Ġunju", "Lulju", "Awwissu", "Settembru", "Ottubru", "Novembru", "Diċembru"} // monthNamesMalteseAbbr list the month name abbreviations in the Maltese. monthNamesMalteseAbbr = []string{"Jan", "Fra", "Mar", "Apr", "Mej", "Ġun", "Lul", "Aww", "Set", "Ott", "Nov", "Diċ"} // monthNamesMaori list the month names in the Maori. monthNamesMaori = []string{"Kohitātea", "Huitanguru", "Poutūterangi", "Paengawhāwhā", "Haratua", "Pipiri", "Hōngongoi", "Hereturikōkā", "Mahuru", "Whiringa ā-nuku", "Whiringa ā-rangi", "Hakihea"} // monthNamesMaoriAbbr list the month name abbreviations in the Maori. monthNamesMaoriAbbr = []string{"Kohi", "Hui", "Pou", "Pae", "Hara", "Pipi", "Hōngo", "Here", "Mahu", "Nuku", "Rangi", "Haki"} // monthNamesMapudungun list the month name abbreviations in the Mapudungun. monthNamesMapudungun = []string{"Kiñe Tripantu", "Epu", "Kila", "Meli", "Kechu", "Cayu", "Regle", "Purha", "Aiya", "Marhi", "Marhi Kiñe", "Marhi Epu"} // monthNamesMarathi list the month names in the Marathi. monthNamesMarathi = []string{ "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940", "\u092B\u0947\u092C\u094D\u0930\u0941\u0935\u093E\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u090F\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u0948", "\u0911\u0917\u0938\u094D\u091F", "\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930", "\u0911\u0915\u094D\u091F\u094B\u092C\u0930", "\u0928\u094B\u0935\u094D\u0939\u0947\u0902\u092C\u0930", "\u0921\u093F\u0938\u0947\u0902\u092C\u0930", } // monthNamesMarathiAbbr lists the month name abbreviations in Marathi. monthNamesMarathiAbbr = []string{ "\u091C\u093E\u0928\u0947.", "\u092B\u0947\u092C\u094D\u0930\u0941.", "\u092E\u093E\u0930\u094D\u091A", "\u090F\u092A\u094D\u0930\u093F", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u0948", "\u0911\u0917.", "\u0938\u092A\u094D\u091F\u0947\u0902.", "\u0911\u0915\u094D\u091F\u094B.", "\u0928\u094B\u0935\u094D\u0939\u0947\u0902.", "\u0921\u093F\u0938\u0947\u0902.", } // monthNamesMohawk list the month names in the Mohawk. monthNamesMohawk = []string{"Tsothohrkó:Wa", "Enniska", "Enniskó:Wa", "Onerahtókha", "Onerahtohkó:Wa", "Ohiari:Ha", "Ohiarihkó:Wa", "Seskéha", "Seskehkó:Wa", "Kenténha", "Kentenhkó:Wa", "Tsothóhrha"} // monthNamesMongolian list the month names in the Mongolian. monthNamesMongolian = []string{ "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440", "\u0425\u043E\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0414\u04E9\u0440\u04E9\u0432\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440", "\u0422\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0417\u0443\u0440\u0433\u0430\u0430\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0414\u043E\u043B\u043E\u043E\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u041D\u0430\u0439\u043C\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0415\u0441\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440", "\u0410\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", "\u0410\u0440\u0432\u0430\u043D \u043D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440", "\u0410\u0440\u0432\u0430\u043D \u0445\u043E\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440", } // monthNamesMongolianAbbr lists the month name abbreviations in Mongolian. monthNamesMongolianAbbr = []string{ "1-\u0440 \u0441\u0430\u0440", "2-\u0440 \u0441\u0430\u0440", "3-\u0440 \u0441\u0430\u0440", "4-\u0440 \u0441\u0430\u0440", "5-\u0440 \u0441\u0430\u0440", "6-\u0440 \u0441\u0430\u0440", "7-\u0440 \u0441\u0430\u0440", "8-\u0440 \u0441\u0430\u0440", "9-\u0440 \u0441\u0430\u0440", "10-\u0440 \u0441\u0430\u0440", "11-\u0440 \u0441\u0430\u0440", "12-\u0440 \u0441\u0430\u0440", } // monthNamesMoroccoAbbr lists the month name abbreviations in the Morocco. monthNamesMoroccoAbbr = []string{"jan.", "fév.", "mar.", "avr.", "mai", "jui.", "juil.", "août", "sept.", "oct.", "nov.", "déc."} // monthNamesNepali list the month names in the Nepali. monthNamesNepali = []string{ "\u091C\u0928\u0935\u0930\u0940", "\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u0905\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u093E\u0908", "\u0905\u0917\u0938\u094D\u0924", "\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930", "\u0905\u0915\u094D\u091F\u094B\u092C\u0930", "\u0928\u094B\u092D\u0947\u092E\u094D\u092C\u0930", "\u0921\u093F\u0938\u0947\u092E\u094D\u092C\u0930", } // monthNamesNepaliAbbr lists the month name abbreviations in the Nepali. monthNamesNepaliAbbr = []string{ "\u091C\u0928", "\u092B\u0947\u092C", "\u092E\u093E\u0930\u094D\u091A", "\u0905\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u093E\u0908", "\u0905\u0917", "\u0938\u0947\u092A\u094D\u091F", "\u0905\u0915\u094D\u091F", "\u0928\u094B\u092D", "\u0921\u093F\u0938", } // monthNamesNepaliIN list the month names in the India Nepali. monthNamesNepaliIN = []string{ "\u091C\u0928\u0935\u0930\u0940", "\u092B\u0930\u0935\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u0905\u092A\u094D\u0930\u0947\u0932", "\u092E\u0908", "\u091C\u0941\u0928", "\u091C\u0941\u0932\u093E\u0908", "\u0905\u0917\u0938\u094D\u091F", "\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930", "\u0905\u0915\u094D\u091F\u094B\u092C\u0930", "\u0928\u094B\u092D\u0947\u092E\u094D\u092C\u0930", "\u0926\u093F\u0938\u092E\u094D\u092C\u0930", } // monthNamesNepaliINAbbr lists the month name abbreviations in the India Nepali. monthNamesNepaliINAbbr = []string{ "\u091C\u0928\u0935\u0930\u0940", "\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u0905\u092A\u094D\u0930\u093F", "\u092E\u0947", "\u091C\u0941\u0928", "\u091C\u0941\u0932\u093E", "\u0905\u0917\u0938\u094D\u091F", "\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930", "\u0905\u0915\u094D\u091F\u094B", "\u0928\u094B\u092D\u0947", "\u0921\u093F\u0938\u0947", } // monthNamesNigeria list the month names in the Nigeria. monthNamesNigeria = []string{"samwiee", "feeburyee", "marsa", "awril", "me", "suyeŋ", "sulyee", "ut", "satambara", "oktoobar", "nowamburu", "deesamburu"} // monthNamesNigeriaAbbr lists the month name abbreviations in the Nigeria. monthNamesNigeriaAbbr = []string{"samw", "feeb", "mar", "awr", "me", "suy", "sul", "ut", "sat", "okt", "now", "dees"} // monthNamesNorwegian list the month names in the Norwegian. monthNamesNorwegian = []string{"januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember"} // monthNamesOccitan list the month names in the Occitan. monthNamesOccitan = []string{"genièr", "febrièr", "març", "abril", "mai", "junh", "julhet", "agost", "setembre", "octobre", "novembre", "decembre"} // monthNamesOccitanAbbr lists the month name abbreviations in the Occitan. monthNamesOccitanAbbr = []string{"gen.", "feb.", "març", "abr.", "mai", "junh", "julh", "ag.", "set.", "oct.", "nov.", "dec."} // monthNamesOdia list the month names in the Odia. monthNamesOdia = []string{ "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40", "\u0B2B\u0B47\u0B2C\u0B43\u0B06\u0B30\u0B40", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A", "\u0B0F\u0B2A\u0B4D\u0B30\u0B3F\u0B32\u0B4D\u200C", "\u0B2E\u0B47", "\u0B1C\u0B41\u0B28\u0B4D\u200C", "\u0B1C\u0B41\u0B32\u0B3E\u0B07", "\u0B05\u0B17\u0B37\u0B4D\u0B1F", "\u0B38\u0B47\u0B2A\u0B4D\u0B1F\u0B47\u0B2E\u0B4D\u0B2C\u0B30", "\u0B05\u0B15\u0B4D\u0B1F\u0B4B\u0B2C\u0B30", "\u0B28\u0B2D\u0B47\u0B2E\u0B4D\u0B2C\u0B30", "\u0B21\u0B3F\u0B38\u0B47\u0B2E\u0B4D\u0B2C\u0B30", } // monthNamesOromo list the month names in the Oromo. monthNamesOromo = []string{"Amajjii", "Guraandhala", "Bitooteessa", "Elba", "Caamsa", "Waxabajjii", "Adooleessa", "Hagayya", "Fuulbana", "Onkololeessa", "Sadaasa", "Muddee"} // monthNamesOromoAbbr list the month abbreviations in the Oromo. monthNamesOromoAbbr = []string{"Ama", "Gur", "Bit", "Elb", "Cam", "Wax", "Ado", "Hag", "Ful", "Onk", "Sad", "Mud"} // monthNamesPashto list the month names in the Pashto. monthNamesPashto = []string{ "\u0633\u0644\u0648\u0627\u063A\u0647", "\u0643\u0628", "\u0648\u0631\u0649", "\u063A\u0648\u064A\u0649", "\u063A\u0628\u0631\u06AB\u0648\u0644\u0649", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649", "\u0632\u0645\u0631\u0649", "\u0648\u0696\u0649", "\u062A\u0644\u0647", "\u0644\u0693\u0645", "\u0644\u0646\u0688 \u06CD", "\u0645\u0631\u063A\u0648\u0645\u0649", } // monthNamesPersian list the month names in the Persian. monthNamesPersian = []string{ "\u0698\u0627\u0646\u0648\u064A\u0647", "\u0641\u0648\u0631\u064A\u0647", "\u0645\u0627\u0631\u0633", "\u0622\u0648\u0631\u064A\u0644", "\u0645\u0647", "\u0698\u0648\u0626\u0646", "\u0698\u0648\u0626\u064A\u0647", "\u0627\u0648\u062A", "\u0633\u067E\u062A\u0627\u0645\u0628\u0631", "\u0627\u064F\u0643\u062A\u0628\u0631", "\u0646\u0648\u0627\u0645\u0628\u0631", "\u062F\u0633\u0627\u0645\u0628\u0631", } // monthNamesPolish list the month names in the Polish. monthNamesPolish = []string{"styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"} // monthNamesPortuguese list the month names in the Portuguese. monthNamesPortuguese = []string{"janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"} // monthNamesPunjabi list the month names in the Punjabi. monthNamesPunjabi = []string{ "\u0A1C\u0A28\u0A35\u0A30\u0A40", "\u0A2B\u0A3C\u0A30\u0A35\u0A30\u0A40", "\u0A2E\u0A3E\u0A30\u0A1A", "\u0A05\u0A2A\u0A4D\u0A30\u0A48\u0A32", "\u0A2E\u0A08", "\u0A1C\u0A42\u0A28", "\u0A1C\u0A41\u0A32\u0A3E\u0A08", "\u0A05\u0A17\u0A38\u0A24", "\u0A38\u0A24\u0A70\u0A2C\u0A30", "\u0A05\u0A15\u0A24\u0A42\u0A2C\u0A30", "\u0A28\u0A35\u0A70\u0A2C\u0A30", "\u0A26\u0A38\u0A70\u0A2C\u0A30", } // monthNamesPunjabiArab list the month names in the Punjabi Arab. monthNamesPunjabiArab = []string{ "\u062C\u0646\u0648\u0631\u06CC", "\u0641\u0631\u0648\u0631\u06CC", "\u0645\u0627\u0631\u0686", "\u0627\u067E\u0631\u06CC\u0644", "\u0645\u0626\u06CC", "\u062C\u0648\u0646", "\u062C\u0648\u0644\u0627\u0626\u06CC", "\u0627\u06AF\u0633\u062A", "\u0633\u062A\u0645\u0628\u0631", "\u0627\u06A9\u062A\u0648\u0628\u0631", "\u0646\u0648\u0645\u0628\u0631", "\u062F\u0633\u0645\u0628\u0631", } // monthNamesQuechua list the month names in the Quechua. monthNamesQuechua = []string{"Qulla puquy", "Hatun puquy", "Pauqar waray", "ayriwa", "Aymuray", "Inti raymi", "Anta Sitwa", "Qhapaq Sitwa", "Uma raymi", "Kantaray", "Ayamarq'a", "Kapaq Raymi"} // monthNamesQuechuaEcuador list the month names in the Quechua Ecuador. monthNamesQuechuaEcuador = []string{"kulla", "panchi", "pawkar", "ayriwa", "aymuray", "raymi", "sitwa", "karwa", "kuski", "wayru", "sasi", "kapak"} // monthNamesRomanian list the month names in the Romanian. monthNamesRomanian = []string{"ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie"} // monthNamesRomanianAbbr list the month abbreviations in the Romanian. monthNamesRomanianAbbr = []string{"ian.", "feb.", "mar.", "apr.", "mai", "iun.", "iul.", "aug.", "sept.", "oct.", "nov.", "dec."} // monthNamesRomansh list the month names in the Romansh. monthNamesRomansh = []string{"schaner", "favrer", "mars", "avrigl", "matg", "zercladur", "fanadur", "avust", "settember", "october", "november", "december"} // monthNamesRomanshAbbr list the month abbreviations in the Romansh. monthNamesRomanshAbbr = []string{"schan.", "favr.", "mars", "avr.", "matg", "zercl.", "fan.", "avust", "sett.", "oct.", "nov.", "dec."} // monthNamesRussian list the month names in the Russian. monthNamesRussian = []string{ "\u044F\u043D\u0432\u0430\u0440\u044C", "\u0444\u0435\u0432\u0440\u0430\u043B\u044C", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0435\u043B\u044C", "\u043C\u0430\u0439", "\u0438\u044E\u043D\u044C", "\u0438\u044E\u043B\u044C", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C", "\u043E\u043A\u0442\u044F\u0431\u0440\u044C", "\u043D\u043E\u044F\u0431\u0440\u044C", "\u0434\u0435\u043A\u0430\u0431\u0440\u044C", } // monthNamesRussianAbbr list the month abbreviations in the Russian. monthNamesRussianAbbr = []string{ "\u044F\u043D\u0432.", "\u0444\u0435\u0432.", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440.", "\u043C\u0430\u0439", "\u0438\u044E\u043D\u044C", "\u0438\u044E\u043B\u044C", "\u0430\u0432\u0433.", "\u0441\u0435\u043D.", "\u043E\u043A\u0442.", "\u043D\u043E\u044F.", "\u0434\u0435\u043A.", } // monthNamesSakha list the month names in the Sakha. monthNamesSakha = []string{ "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443", "\u041E\u043B\u0443\u043D\u043D\u044C\u0443", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440", "\u041C\u0443\u0443\u0441 \u0443\u0441\u0442\u0430\u0440", "\u042B\u0430\u043C \u044B\u0439\u0430", "\u0411\u044D\u0441 \u044B\u0439\u0430", "\u041E\u0442 \u044B\u0439\u0430", "\u0410\u0442\u044B\u0440\u0434\u044C\u0430\u0445 \u044B\u0439\u0430", "\u0411\u0430\u043B\u0430\u0495\u0430\u043D \u044B\u0439\u0430", "\u0410\u043B\u0442\u044B\u043D\u043D\u044C\u044B", "\u0421\u044D\u0442\u0438\u043D\u043D\u044C\u0438", "\u0410\u0445\u0441\u044B\u043D\u043D\u044C\u044B", } // monthNamesSakhaAbbr list the month abbreviations in the Sakha. monthNamesSakhaAbbr = []string{ "\u0422\u0445\u0441", "\u041E\u043B\u043D", "\u041A\u043B\u043D", "\u041C\u0441\u0443", "\u042B\u0430\u043C", "\u0411\u044D\u0441", "\u041E\u0442\u044B", "\u0410\u0442\u0440", "\u0411\u043B\u0495", "\u0410\u043B\u0442", "\u0421\u044D\u0442", "\u0410\u0445\u0441", } // monthNamesSami list the month names in the Sami. monthNamesSami = []string{"uđđâivemáánu", "kuovâmáánu", "njuhčâmáánu", "cuáŋuimáánu", "vyesimáánu", "kesimáánu", "syeinimáánu", "porgemáánu", "čohčâmáánu", "roovvâdmáánu", "skammâmáánu", "juovlâmáánu"} // monthNamesSamiAbbr list the month abbreviations in the Sami. monthNamesSamiAbbr = []string{"uđiv", "kuov", "njuh", "cuáŋ", "vyes", "kesi", "syei", "porg", "čohč", "roov", "skam", "juov"} // monthNamesSamiLule list the month names in the Sami (Lule). monthNamesSamiLule = []string{"ådåjakmánno", "guovvamánno", "sjnjuktjamánno", "vuoratjismánno", "moarmesmánno", "biehtsemánno", "sjnjilltjamánno", "bårggemánno", "ragátmánno", "gålgådismánno", "basádismánno", "javllamánno"} // monthNamesSamiLuleAbbr list the month abbreviations in the Sami (Lule). monthNamesSamiLuleAbbr = []string{"ådåj", "guov", "snju", "vuor", "moar", "bieh", "snji", "bårg", "ragá", "gålg", "basá", "javl"} // monthNamesSamiNorthern list the month names in the Sami (Northern). monthNamesSamiNorthern = []string{"ođđajagemánnu", "guovvamánnu", "njukčamánnu", "cuoŋománnu", "miessemánnu", "geassemánnu", "suoidnemánnu", "borgemánnu", "čakčamánnu", "golggotmánnu", "skábmamánnu", "juovlamánnu"} // monthNamesSamiNorthernAbbr list the month abbreviations in the Sami (Northern). monthNamesSamiNorthernAbbr = []string{"ođđj", "guov", "njuk", "cuoŋ", "mies", "geas", "suoi", "borg", "čakč", "golg", "skáb", "juov"} // monthNamesSamiSkolt list the month names in the Sami (Skolt). monthNamesSamiSkolt = []string{"ođđee´jjmään", "tä´lvvmään", "pâ´zzlâšttam-mään", "njuhččmään", "vue´ssmään", "ǩie´ssmään", "suei´nnmään", "på´rǧǧmään", "čõhččmään", "kålggmään", "skamm-mään", "rosttovmään"} // monthNamesSamiSouthern list the month names in the Sami (Southern). monthNamesSamiSouthern = []string{"tsïengele", "goevte", "njoktje", "voerhtje", "suehpede", "ruffie", "snjaltje", "mïetske", "skïerede", "golke", "rahka", "goeve"} // monthNamesSamiSouthernAbbr list the month abbreviations in the Sami (Southern). monthNamesSamiSouthernAbbr = []string{"tsïen", "goevt", "njok", "voer", "sueh", "ruff", "snja", "mïet", "skïer", "golk", "rahk", "goev"} // monthNamesSanskrit list the month names in the Sanskrit. monthNamesSanskrit = []string{ "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940", "\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940", "\u092E\u093E\u0930\u094D\u091A", "\u090F\u092A\u094D\u0930\u093F\u0932", "\u092E\u0947", "\u091C\u0942\u0928", "\u091C\u0941\u0932\u0948", "\u0911\u0917\u0938\u094D\u091F", "\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930", "\u0911\u0915\u094D\u091F\u094B\u092C\u0930", "\u0928\u094B\u0935\u094D\u0939\u0947\u0902\u092C\u0930", "\u0921\u093F\u0938\u0947\u0902\u092C\u0930", } // monthNamesScottishGaelic list the month names in the Scottish Gaelic. monthNamesScottishGaelic = []string{"Am Faoilleach", "An Gearran", "Am Màrt", "An Giblean", "An Cèitean", "An t-Ògmhios", "An t-Iuchar", "An Lùnastal", "An t-Sultain", "An Dàmhair", "An t-Samhain", "An Dùbhlachd"} // monthNamesScottishGaelicAbbr list the month abbreviations in the ScottishGaelic. monthNamesScottishGaelicAbbr = []string{"Faoi", "Gear", "Màrt", "Gibl", "Cèit", "Ògmh", "Iuch", "Lùna", "Sult", "Dàmh", "Samh", "Dùbh"} // monthNamesSerbian list the month names in the Serbian (Cyrillic). monthNamesSerbian = []string{ "\u0458\u0430\u043D\u0443\u0430\u0440", "\u0444\u0435\u0431\u0440\u0443\u0430\u0440", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0438\u043B", "\u043C\u0430\u0458", "\u0458\u0443\u043D", "\u0458\u0443\u043B", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440", "\u043E\u043A\u0442\u043E\u0431\u0430\u0440", "\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440", "\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440", } // monthNamesSerbianAbbr lists the month name abbreviations in the Serbian // (Cyrillic). monthNamesSerbianAbbr = []string{ "\u0458\u0430\u043D.", "\u0444\u0435\u0431.", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440.", "\u043C\u0430\u0458", "\u0458\u0443\u043D", "\u0458\u0443\u043B", "\u0430\u0432\u0433.", "\u0441\u0435\u043F\u0442.", "\u043E\u043A\u0442.", "\u043D\u043E\u0432.", "\u0434\u0435\u0446.", } // monthNamesSerbianBA list the month names in the Serbian (Cyrillic) Bosnia // and Herzegovina. monthNamesSerbianBA = []string{ "\u0458\u0430\u043D\u0443\u0430\u0440", "\u0444\u0435\u0431\u0440\u0443\u0430\u0440", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0438\u043B", "\u043C\u0430\u0458", "\u0458\u0443\u043D\u0438", "\u0458\u0443\u043B\u0438", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440", "\u043E\u043A\u0442\u043E\u0431\u0430\u0440", "\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440", "\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440", } // monthNamesSerbianBAAbbr lists the month name abbreviations in the Serbian // (Cyrillic) Bosnia and Herzegovina. monthNamesSerbianBAAbbr = []string{ "\u0458\u0430\u043D", "\u0444\u0435\u0431", "\u043C\u0430\u0440", "\u0430\u043F\u0440", "\u043C\u0430\u0458", "\u0458\u0443\u043D", "\u0458\u0443\u043B", "\u0430\u0432\u0433", "\u0441\u0435\u043F", "\u043E\u043A\u0442", "\u043D\u043E\u0432", "\u0434\u0435\u0446", } // monthNamesSerbianLatin list the month names in the Serbian (Latin). monthNamesSerbianLatin = []string{"januar", "februar", "mart", "april", "maj", "jun", "jul", "avgust", "septembar", "oktobar", "novembar", "decembar"} // monthNamesSerbianLatinAbbr lists the month name abbreviations in the // Serbian(Latin) and Montenegro (Former). monthNamesSerbianLatinAbbr = []string{"jan.", "feb.", "mart", "apr.", "maj", "jun", "jul", "avg.", "sept.", "okt.", "nov.", "dec."} // monthNamesSesothoSaLeboa list the month names in the Sesotho sa Leboa. monthNamesSesothoSaLeboa = []string{"Janaware", "Feberware", "Matšhe", "Aprele", "Mei", "June", "Julae", "Agostose", "Setemere", "Oktoboro", "Nofemere", "Disemere"} // monthNamesSesothoSaLeboaAbbr lists the month name abbreviations in the // Sesotho sa Leboa. monthNamesSesothoSaLeboaAbbr = []string{"Jan", "Feb", "Matš", "Apr", "Mei", "June", "Julae", "Agost", "Set", "Oky", "Nof", "Dis"} // monthNamesSetswana list the month names in the Setswana. monthNamesSetswana = []string{"Ferikgong", "Tlhakole", "Mopitlwe", "Moranang", "Motsheganang", "Seetebosigo", "Phukwi", "Phatwe", "Lwetse", "Diphalane", "Ngwanatsele", "Sedimonthole"} // monthNamesSetswanaAbbr lists the month name abbreviations in the Setswana. monthNamesSetswanaAbbr = []string{"Fer.", "Tlh.", "Mop.", "Mor.", "Motsh.", "Seet.", "Phk.", "Pht.", "Lwetse.", "Diph.", "Ngwn.", "Sed."} // monthNamesSindhi list the month names in the Sindhi. monthNamesSindhi = []string{ "\u062C\u0646\u0648\u0631\u064A", "\u0641\u0631\u0648\u0631\u064A", "\u0645\u0627\u0631\u0686", "\u0627\u067E\u0631\u064A\u0644", "\u0645\u0654\u064A", "\u062C\u0648\u0646", "\u062C\u0648\u0644\u0627\u0621\u0650", "\u0622\u06AF\u0633\u062A", "\u0633\u062A\u0645\u0628\u0631", "\u0622\u06A9\u062A\u0648\u0628\u0631", "\u0646\u0648\u0645\u0628\u0631", "\u068A\u0633\u0645\u0628\u0631", } // monthNamesSinhala list the month names in the Sinhala. monthNamesSinhala = []string{ "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2", "\u0DB4\u0DD9\u0DB6\u0DBB\u0DC0\u0DCF\u0DBB\u0DD2", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4", "\u0D85\u0DB4\u0DCA\u200D\u0DBB\u0DDA\u0DBD\u0DCA", "\u0DB8\u0DD0\u0DBA\u0DD2", "\u0DA2\u0DD6\u0DB1\u0DD2", "\u0DA2\u0DD6\u0DBD\u0DD2", "\u0D85\u0D9C\u0DDD\u0DC3\u0DCA\u0DAD\u0DD4", "\u0DC3\u0DD0\u0DB4\u0DCA\u0DAD\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA", "\u0D94\u0D9A\u0DCA\u0DAD\u0DDD\u0DB6\u0DBB\u0DCA", "\u0DB1\u0DDC\u0DC0\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA", "\u0DAF\u0DD9\u0DC3\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA", } // monthNamesSinhalaAbbr lists the month name abbreviations in Sinhala. monthNamesSinhalaAbbr = []string{ "\u0DA2\u0DB1.", "\u0DB4\u0DD9\u0DB6.", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4.", "\u0D85\u0DB4\u0DCA\u200D\u0DBB\u0DDA\u0DBD\u0DCA.", "\u0DB8\u0DD0\u0DBA\u0DD2", "\u0DA2\u0DD6\u0DB1\u0DD2", "\u0DA2\u0DD6\u0DBD\u0DD2", "\u0D85\u0D9C\u0DDD.", "\u0DC3\u0DD0\u0DB4\u0DCA.", "\u0D94\u0D9A\u0DCA.", "\u0DB1\u0DDC\u0DC0\u0DD0.", "\u0DAF\u0DD9\u0DC3\u0DD0.", } // monthNamesSlovak list the month names in the Slovak. monthNamesSlovak = []string{"január", "február", "marec", "apríl", "máj", "jún", "júl", "august", "september", "október", "november", "december"} // monthNamesSlovenian list the month names in the Slovenian. monthNamesSlovenian = []string{"januar", "februar", "marec", "april", "maj", "junij", "julij", "avgust", "september", "oktober", "november", "december"} // monthNamesSlovenianAbbr list the month abbreviations in the Slovenian. monthNamesSlovenianAbbr = []string{"jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "avg.", "sep.", "okt.", "nov.", "dec."} // monthNamesSomali list the month names in the Somali. monthNamesSomali = []string{"Jannaayo", "Febraayo", "Maarso", "Abriil", "May", "Juun", "Luuliyo", "Ogost", "Sebtembar", "Oktoobar", "Nofembar", "Desembar"} // monthNamesSomaliAbbr list the month abbreviations in the Somali. monthNamesSomaliAbbr = []string{"Jan", "Feb", "Mar", "Abr", "May", "Jun", "Lul", "Ogs", "Seb", "Okt", "Nof", "Dis"} // monthNamesSotho list the month names in the Sotho. monthNamesSotho = []string{"Phesekgong", "Hlakola", "Hlakubele", "Mmese", "Motsheanong", "Phupjane", "Phupu", "Phata", "Leotshe", "Mphalane", "Pundungwane", "Tshitwe"} // monthNamesSothoAbbr list the month abbreviations in the Sotho. monthNamesSothoAbbr = []string{"Phe", "Kol", "Ube", "Mme", "Mot", "Jan", "Upu", "Pha", "Leo", "Mph", "Pun", "Tsh"} // monthNamesSpanish list the month names in the Spanish. monthNamesSpanish = []string{"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"} // monthNamesSpanishAbbr list the month abbreviations in the Spanish. monthNamesSpanishAbbr = []string{"ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"} // monthNamesSpanishPE list the month names in the Spanish Peru. monthNamesSpanishPE = []string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Setiembre", "Octubre", "Noviembre", "Diciembre"} // monthNamesSpanishPEAbbr list the month abbreviations in the Spanish Peru. monthNamesSpanishPEAbbr = []string{"Ene.", "Feb.", "Mar.", "Abr.", "May.", "Jun.", "Jul.", "Ago.", "Set.", "Oct.", "Nov.", "Dic."} // monthNamesSwedish list the month names in the Swedish. monthNamesSwedish = []string{"januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december"} // monthNamesSwedishAbbr list the month abbreviations in the Swedish. monthNamesSwedishAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"} // monthNamesSwedishFIAbbr list the month abbreviations in the Swedish Finland. monthNamesSwedishFIAbbr = []string{"jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec."} // monthNamesSyriac list the month names in the Syriac. monthNamesSyriac = []string{ "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D", "\u072B\u0712\u071B", "\u0710\u0715\u072A", "\u0722\u071D\u0723\u0722", "\u0710\u071D\u072A", "\u071A\u0719\u071D\u072A\u0722", "\u072C\u0721\u0718\u0719", "\u0710\u0712", "\u0710\u071D\u0720\u0718\u0720", "\u072C\u072B\u072A\u071D \u0729\u0715\u071D\u0721", "\u072C\u072B\u072A\u071D \u0710\u071A\u072A\u071D", "\u071F\u0722\u0718\u0722 \u0729\u0715\u071D\u0721", } // monthNamesSyriacAbbr lists the month name abbreviations in the Syriac. monthNamesSyriacAbbr = []string{ "\u071F\u0722 \u070F\u0712", "\u072B\u0712\u071B", "\u0710\u0715\u072A", "\u0722\u071D\u0723\u0722", "\u0710\u071D\u072A", "\u071A\u0719\u071D\u072A\u0722", "\u072C\u0721\u0718\u0719", "\u0710\u0712", "\u0710\u071D\u0720\u0718\u0720", "\u070F\u072C\u072B \u070F\u0710", "\u070F\u072C\u072B \u070F\u0712", "\u070F\u071F\u0722 \u070F\u0710", } // monthNamesSyllabics list the month names in the Syllabics. monthNamesSyllabics = []string{ "\u152E\u14D0\u14C4\u140A\u1546", "\u1556\u155D\u1557\u140A\u1546", "\u14AB\u1466\u14EF", "\u1404\u1433\u1546", "\u14AA\u1403", "\u152B\u14C2", "\u152A\u14DA\u1403", "\u140B\u14A1\u148C\u14EF", "\u14EF\u144E\u1431\u1546", "\u1405\u1450\u1431\u1546", "\u14C4\u1555\u1431\u1546", "\u144E\u14EF\u1431\u1546", } // monthNamesSyllabicsAbbr lists the month name abbreviations in the Syllabics. monthNamesSyllabicsAbbr = []string{ "\u152E\u14D0\u14C4", "\u1556\u155D\u1557", "\u14AB\u1466\u14EF", "\u1404\u1433\u1546", "\u14AA\u1403", "\u152B\u14C2", "\u152A\u14DA\u1403", "\u140B\u14A1\u148C", "\u14EF\u144E\u1431", "\u1405\u1450\u1431", "\u14C4\u1555\u1431", "\u144E\u14EF\u1431", } // monthNamesTajik list the month names in the Tajik. monthNamesTajik = []string{ "\u044F\u043D\u0432\u0430\u0440", "\u0444\u0435\u0432\u0440\u0430\u043B", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0435\u043B", "\u043C\u0430\u0439", "\u0438\u044E\u043D", "\u0438\u044E\u043B", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043D\u0442\u044F\u0431\u0440", "\u043E\u043A\u0442\u044F\u0431\u0440", "\u043D\u043E\u044F\u0431\u0440", "\u0434\u0435\u043A\u0430\u0431\u0440", } // monthNamesTajikAbbr lists the month name abbreviations in Tajik. monthNamesTajikAbbr = []string{ "\u044F\u043D\u0432", "\u0444\u0435\u0432", "\u043C\u0430\u0440", "\u0430\u043F\u0440", "\u043C\u0430\u0439", "\u0438\u044E\u043D", "\u0438\u044E\u043B", "\u0430\u0432\u0433", "\u0441\u0435\u043D", "\u043E\u043A\u0442", "\u043D\u043E\u044F", "\u0434\u0435\u043A", } // monthNamesTamazight list the month names in the Tamazight. monthNamesTamazight = []string{"Yennayer", "Furar", "Meghres", "Yebrir", "Magu", "Yunyu", "Yulyu", "Ghuct", "Cutenber", "Tuber", "Nunember", "Dujanbir"} // monthNamesTamazightAbbr list the month abbreviations in the Tamazight. monthNamesTamazightAbbr = []string{"Yen", "Fur", "Megh", "Yeb", "May", "Yun", "Yul", "Ghu", "Cut", "Tub", "Nun", "Duj"} // monthNamesTamil list the month names in the Tamil. monthNamesTamil = []string{ "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF", "\u0BAA\u0BBF\u0BAA\u0BCD\u0BB0\u0BB5\u0BB0\u0BBF", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD", "\u0B8F\u0BAA\u0BCD\u0BB0\u0BB2\u0BCD", "\u0BAE\u0BC7", "\u0B9C\u0BC2\u0BA9\u0BCD", "\u0B9C\u0BC2\u0BB2\u0BC8", "\u0B86\u0B95\u0BB8\u0BCD\u0B9F\u0BCD", "\u0B9A\u0BC6\u0BAA\u0BCD\u0B9F\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD", "\u0B85\u0B95\u0BCD\u0B9F\u0BCB\u0BAA\u0BB0\u0BCD", "\u0BA8\u0BB5\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD", "\u0B9F\u0BBF\u0B9A\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD", } // monthNamesTamilAbbr lists the month name abbreviations in Tamil. monthNamesTamilAbbr = []string{ "\u0B9C\u0BA9.", "\u0BAA\u0BBF\u0BAA\u0BCD.", "\u0BAE\u0BBE\u0BB0\u0BCD.", "\u0B8F\u0BAA\u0BCD.", "\u0BAE\u0BC7", "\u0B9C\u0BC2\u0BA9\u0BCD", "\u0B9C\u0BC2\u0BB2\u0BC8", "\u0B86\u0B95.", "\u0B9A\u0BC6\u0BAA\u0BCD.", "\u0B85\u0B95\u0BCD.", "\u0BA8\u0BB5.", "\u0B9F\u0BBF\u0B9A.", } // monthNamesTatar list the month names in the Tatar. monthNamesTatar = []string{ "\u0433\u044B\u0439\u043D\u0432\u0430\u0440", "\u0444\u0435\u0432\u0440\u0430\u043B\u044C", "\u043C\u0430\u0440\u0442", "\u0430\u043F\u0440\u0435\u043B\u044C", "\u043C\u0430\u0439", "\u0438\u044E\u043D\u044C", "\u0438\u044E\u043B\u044C", "\u0430\u0432\u0433\u0443\u0441\u0442", "\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C", "\u043E\u043A\u0442\u044F\u0431\u0440\u044C", "\u043D\u043E\u044F\u0431\u0440\u044C", "\u0434\u0435\u043A\u0430\u0431\u0440\u044C", } // monthNamesTatarAbbr lists the month name abbreviations in the Tatar. monthNamesTatarAbbr = []string{ "\u0433\u044B\u0439\u043D.", "\u0444\u0435\u0432.", "\u043C\u0430\u0440.", "\u0430\u043F\u0440.", "\u043C\u0430\u0439", "\u0438\u044E\u043D\u044C", "\u0438\u044E\u043B\u044C", "\u0430\u0432\u0433.", "\u0441\u0435\u043D.", "\u043E\u043A\u0442.", "\u043D\u043E\u044F\u0431.", "\u0434\u0435\u043A.", } // monthNamesTelugu list the month names in the Telugu. monthNamesTelugu = []string{ "\u0C1C\u0C28\u0C35\u0C30\u0C3F", "\u0C2B\u0C3F\u0C2C\u0C4D\u0C30\u0C35\u0C30\u0C3F", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F", "\u0C0F\u0C2A\u0C4D\u0C30\u0C3F\u0C32\u0C4D", "\u0C2E\u0C47", "\u0C1C\u0C42\u0C28\u0C4D", "\u0C1C\u0C41\u0C32\u0C48", "\u0C06\u0C17\u0C38\u0C4D\u0C1F\u0C41", "\u0C38\u0C46\u0C2A\u0C4D\u0C1F\u0C46\u0C02\u0C2C\u0C30\u0C4D", "\u0C05\u0C15\u0C4D\u0C1F\u0C4B\u0C2C\u0C30\u0C4D", "\u0C28\u0C35\u0C02\u0C2C\u0C30\u0C4D", "\u0C21\u0C3F\u0C38\u0C46\u0C02\u0C2C\u0C30\u0C4D", } // monthNamesTeluguAbbr lists the month name abbreviations in the Telugu. monthNamesTeluguAbbr = []string{ "\u0C1C\u0C28", "\u0C2B\u0C3F\u0C2C\u0C4D\u0C30", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F", "\u0C0F\u0C2A\u0C4D\u0C30\u0C3F", "\u0C2E\u0C47", "\u0C1C\u0C42\u0C28\u0C4D", "\u0C1C\u0C41\u0C32\u0C48", "\u0C06\u0C17", "\u0C38\u0C46\u0C2A\u0C4D\u0C1F\u0C46\u0C02", "\u0C05\u0C15\u0C4D\u0C1F\u0C4B", "\u0C28\u0C35\u0C02", "\u0C21\u0C3F\u0C38\u0C46\u0C02", } // monthNamesThai list the month names in the Thai. monthNamesThai = []string{ "\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21", "\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C", "\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21", "\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19", "\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21", "\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19", "\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21", "\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21", "\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19", "\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21", "\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19", "\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21", } // monthNamesTibetan list the month names in the Tibetan. monthNamesTibetan = []string{ "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B", } // monthNamesTibetanAbbr lists the month name abbreviations in the Tibetan. monthNamesTibetanAbbr = []string{ "\u0F5F\u0FB3\u0F0B \u0F21", "\u0F5F\u0FB3\u0F0B \u0F22", "\u0F5F\u0FB3\u0F0B \u0F23", "\u0F5F\u0FB3\u0F0B \u0F24", "\u0F5F\u0FB3\u0F0B \u0F25", "\u0F5F\u0FB3\u0F0B \u0F26", "\u0F5F\u0FB3\u0F0B \u0F27", "\u0F5F\u0FB3\u0F0B \u0F28", "\u0F5F\u0FB3\u0F0B \u0F29", "\u0F5F\u0FB3\u0F0B \u0F21\u0F20", "\u0F5F\u0FB3\u0F0B \u0F21\u0F21", "\u0F5F\u0FB3\u0F0B \u0F21\u0F22", } // monthNamesTigrinya list the month names in the Tigrinya. monthNamesTigrinya = []string{ "\u1325\u122A", "\u1208\u12AB\u1272\u1275", "\u1218\u130B\u1262\u1275", "\u121A\u12EB\u12DD\u12EB", "\u130D\u1295\u1266\u1275", "\u1230\u1290", "\u1213\u121D\u1208", "\u1290\u1213\u1230", "\u1218\u1235\u12A8\u1228\u121D", "\u1325\u1245\u121D\u1272", "\u1215\u12F3\u122D", "\u1273\u1215\u1233\u1235", } // monthNamesTigrinyaAbbr lists the month name abbreviations in the // Tigrinya. monthNamesTigrinyaAbbr = []string{ "\u1325\u122A", "\u1208\u12AB", "\u1218\u130B", "\u121A\u12EB", "\u130D\u1295", "\u1230\u1290", "\u1213\u121D", "\u1290\u1213", "\u1218\u1235", "\u1325\u1245", "\u1215\u12F3", "\u1273\u1215", } // monthNamesTsonga list the month names in the Tsonga. monthNamesTsonga = []string{"Sunguti", "Nyenyenyani", "Nyenyankulu", "Dzivamisoko", "Mudyaxihi", "Khotavuxika", "Mawuwani", "Mhawuri", "Ndzhati", "Nhlangula", "Hukuri", "N\u2019wendzamhala"} // monthNamesTsongaAbbr lists the month name abbreviations in Tsonga, this // prevents string concatenation. monthNamesTsongaAbbr = []string{"Sun", "Yan", "Kul", "Dzi", "Mud", "Kho", "Maw", "Mha", "Ndz", "Nhl", "Huk", "N\u2019w"} // monthNamesTradMongolian lists the month number for use with traditional // Mongolian. monthNamesTradMongolian = []string{"M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12"} // monthNamesTurkish list the month names in the Turkish. monthNamesTurkish = []string{"Ocak", "Şubat", "Mart", "Nisan", "May\u0131s", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kas\u0131m", "Aral\u0131k"} // monthNamesTurkishAbbr lists the month name abbreviations in Turkish, this // prevents string concatenation. monthNamesTurkishAbbr = []string{"Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"} // monthNamesTurkmen list the month names in the Turkmen. monthNamesTurkmen = []string{"Ýanwar", "Fewral", "Mart", "Aprel", "Maý", "lýun", "lýul", "Awgust", "Sentýabr", "Oktýabr", "Noýabr", "Dekabr"} // monthNamesTurkmenAbbr lists the month name abbreviations in Turkmen, this // prevents string concatenation. monthNamesTurkmenAbbr = []string{"Ýan", "Few", "Mart", "Apr", "Maý", "lýun", "lýul", "Awg", "Sen", "Okt", "Noý", "Dek"} // monthNamesUkrainian list the month names in the Ukrainian. monthNamesUkrainian = []string{ "\u0441\u0456\u0447\u0435\u043D\u044C", "\u043B\u044E\u0442\u0438\u0439", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C", "\u043A\u0432\u0456\u0442\u0435\u043D\u044C", "\u0442\u0440\u0430\u0432\u0435\u043D\u044C", "\u0447\u0435\u0440\u0432\u0435\u043D\u044C", "\u043B\u0438\u043F\u0435\u043D\u044C", "\u0441\u0435\u0440\u043F\u0435\u043D\u044C", "\u0432\u0435\u0440\u0435\u0441\u0435\u043D\u044C", "\u0436\u043E\u0432\u0442\u0435\u043D\u044C", "\u043B\u0438\u0441\u0442\u043E\u043F\u0430\u0434", "\u0433\u0440\u0443\u0434\u0435\u043D\u044C", } // monthNamesUkrainianAbbr lists the month name abbreviations in Ukrainian. monthNamesUkrainianAbbr = []string{ "\u0421\u0456\u0447", "\u041B\u044E\u0442", "\u0411\u0435\u0440", "\u041A\u0432\u0456", "\u0422\u0440\u0430", "\u0427\u0435\u0440", "\u041B\u0438\u043F", "\u0421\u0435\u0440", "\u0412\u0435\u0440", "\u0416\u043E\u0432", "\u041B\u0438\u0441", "\u0413\u0440\u0443", } // monthNamesUpperSorbian list the month names in the Upper Sorbian. monthNamesUpperSorbian = []string{"januar", "februar", "měrc", "apryl", "meja", "junij", "julij", "awgust", "september", "oktober", "nowember", "december"} // monthNamesUpperSorbianAbbr lists the month name abbreviations in the // Upper Sorbian, this prevents string concatenation. monthNamesUpperSorbianAbbr = []string{"jan", "feb", "měr", "apr", "mej", "jun", "jul", "awg", "sep", "okt", "now", "dec"} // monthNamesUyghur list the month names in the Uyghur. monthNamesUyghur = []string{ "\u064A\u0627\u0646\u06CB\u0627\u0631", "\u0641\u06D0\u06CB\u0631\u0627\u0644", "\u0645\u0627\u0631\u062A", "\u0626\u0627\u067E\u0631\u06D0\u0644", "\u0645\u0627\u064A", "\u0626\u0649\u064A\u06C7\u0646", "\u0626\u0649\u064A\u06C7\u0644", "\u0626\u0627\u06CB\u063A\u06C7\u0633\u062A", "\u0633\u06D0\u0646\u062A\u06D5\u0628\u0649\u0631", "\u0626\u06C6\u0643\u062A\u06D5\u0628\u0649\u0631", "\u0646\u0648\u064A\u0627\u0628\u0649\u0631", "\u062F\u06D0\u0643\u0627\u0628\u0649\u0631", } // monthNamesUzbek list the month names in the Uzbek. monthNamesUzbek = []string{"Yanvar", "Fevral", "Mart", "Aprel", "May", "Iyun", "Iyul", "Avgust", "Sentabr", "Oktabr", "Noyabr", "Dekabr"} // monthNamesUzbekAbbr lists the month name abbreviations in the Uzbek, this // prevents string concatenation. monthNamesUzbekAbbr = []string{"Yan", "Fev", "Mar", "Apr", "May", "Iyn", "Iyl", "Avg", "Sen", "Okt", "Noy", "Dek"} // monthNamesValencian list the month names in the Valencian. monthNamesValencian = []string{"gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"} // monthNamesValencianAbbr lists the month name abbreviations in the // Valencian, this prevents string concatenation. monthNamesValencianAbbr = []string{"gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des."} // monthNamesVenda list the month names in the Venda. monthNamesVenda = []string{"Phando", "Luhuhi", "Ṱhafamuhwe", "Lambamai", "Shundunthule", "Fulwi", "Fulwana", "Ṱhangule", "Khubvumedzi", "Tshimedzi", "Ḽara", "Nyendavhusiku"} // monthNamesVendaAbbr lists the month name abbreviations in the Venda, this // prevents string concatenation. monthNamesVendaAbbr = []string{"Pha", "Luh", "Ṱhf", "Lam", "Shu", "Lwi", "Lwa", "Ṱha", "Khu", "Tsh", "Ḽar", "Nye"} // monthNamesVietnamese list the month name used for Vietnamese monthNamesVietnamese = []string{"Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12"} // monthNamesVietnameseAbbr3 list the mid-form abbreviation for Vietnamese // months. monthNamesVietnameseAbbr3 = []string{"Thg 1", "Thg 2", "Thg 3", "Thg 4", "Thg 5", "Thg 6", "Thg 7", "Thg 8", "Thg 9", "Thg 10", "Thg 11", "Thg 12"} // monthNamesVietnameseAbbr5 list the short-form abbreviation for Vietnamese // months. monthNamesVietnameseAbbr5 = []string{"T 1", "T 2", "T 3", "T 4", "T 5", "T 6", "T 7", "T 8", "T 9", "T 10", "T 11", "T 12"} // monthNamesWelsh list the month names in the Welsh. monthNamesWelsh = []string{"Ionawr", "Chwefror", "Mawrth", "Ebrill", "Mai", "Mehefin", "Gorffennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr"} // monthNamesWelshAbbr lists the month name abbreviations in the Welsh, this // prevents string concatenation. monthNamesWelshAbbr = []string{"Ion", "Chwef", "Maw", "Ebr", "Mai", "Meh", "Gorff", "Awst", "Medi", "Hyd", "Tach", "Rhag"} // monthNamesWolof list the month names in the Wolof. monthNamesWolof = []string{"Samwiye", "Fewriye", "Maars", "Awril", "Me", "Suwe", "Sullet", "Ut", "Septàmbar", "Oktoobar", "Noowàmbar", "Desàmbar"} // monthNamesWolofAbbr list the month name abbreviations in the Wolof, this // prevents string concatenation. monthNamesWolofAbbr = []string{"Sam.", "Few.", "Maa", "Awr.", "Me", "Suw", "Sul.", "Ut", "Sept.", "Okt.", "Now.", "Des."} // monthNamesXhosa list the month names in the Xhosa. monthNamesXhosa = []string{"uJanuwari", "uFebuwari", "uMatshi", "uAprili", "uMeyi", "uJuni", "uJulayi", "uAgasti", "uSeptemba", "uOktobha", "uNovemba", "uDisemba"} // monthNamesXhosaAbbr list the month abbreviations in the Xhosa, this // prevents string concatenation. monthNamesXhosaAbbr = []string{"uJan.", "uFeb.", "uMat.", "uEpr.", "uMey.", "uJun.", "uJul.", "uAg.", "uSep.", "uOkt.", "uNov.", "uDis."} // monthNamesYi list the month names in the Yi. monthNamesYi = []string{"\uA2CD", "\uA44D", "\uA315", "\uA1D6", "\uA26C", "\uA0D8", "\uA3C3", "\uA246", "\uA22C", "\uA2B0", "\uA2B0\uA2AA", "\uA2B0\uA44B"} // monthNamesYiSuffix lists the month names in Yi with the "\uA1AA" suffix. monthNamesYiSuffix = []string{"\uA2CD\uA1AA", "\uA44D\uA1AA", "\uA315\uA1AA", "\uA1D6\uA1AA", "\uA26C\uA1AA", "\uA0D8\uA1AA", "\uA3C3\uA1AA", "\uA246\uA1AA", "\uA22C\uA1AA", "\uA2B0\uA1AA", "\uA2B0\uA2AA\uA1AA", "\uA2B0\uA44B\uA1AA"} // monthNamesYiddish list the month names in the Yiddish. monthNamesYiddish = []string{ "\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8", "\u05E4\u05BF\u05E2\u05D1\u05E8\u05D5\u05D0\u05B7\u05E8", "\u05DE\u05E2\u05E8\u05E5", "\u05D0\u05B7\u05E4\u05BC\u05E8\u05D9\u05DC", "\u05DE\u05D9\u05D9", "\u05D9\u05D5\u05E0\u05D9", "\u05D9\u05D5\u05DC\u05D9", "\u05D0\u05D5\u05D9\u05D2\u05D5\u05E1\u05D8", "\u05E1\u05E2\u05E4\u05BC\u05D8\u05E2\u05DE\u05D1\u05E2\u05E8", "\u05D0\u05E7\u05D8\u05D0\u05D1\u05E2\u05E8", "\u05E0\u05D0\u05D5\u05D5\u05E2\u05DE\u05D1\u05E2\u05E8", "\u05D3\u05E2\u05E6\u05E2\u05DE\u05D1\u05E2\u05E8", } // monthNamesYiddishAbbr lists the month name abbreviations in Yiddish. monthNamesYiddishAbbr = []string{ "\u05D9\u05D0\u05B7\u05E0", "\u05E4\u05BF\u05E2\u05D1", "\u05DE\u05E2\u05E8\u05E5", "\u05D0\u05B7\u05E4\u05BC\u05E8", "\u05DE\u05D9\u05D9", "\u05D9\u05D5\u05E0\u05D9", "\u05D9\u05D5\u05DC\u05D9", "\u05D0\u05D5\u05D9\u05D2", "\u05E1\u05E2\u05E4\u05BC", "\u05D0\u05E7\u05D8", "\u05E0\u05D0\u05D5\u05D5", "\u05D3\u05E2\u05E6", } // monthNamesYoruba list the month names in the Yoruba. monthNamesYoruba = []string{ "\u1E62\u1EB9\u0301r\u1EB9\u0301", "Èrèlè", "\u1EB8r\u1EB9\u0300nà", "Ìgbé", "\u1EB8\u0300bibi", "Òkúdu", "Age\u0323mo\u0323", "Ògún", "Owewe", "\u1ECC\u0300wàrà", "Bélú", "\u1ECC\u0300p\u1EB9\u0300", } // monthNamesYorubaAbbr lists the month name abbreviations in the Yoruba. monthNamesYorubaAbbr = []string{ "\u1E62\u1EB9\u0301", "Èr", "\u1EB8r", "Ìg", "\u1EB8\u0300b", "Òk", "Ag", "Òg", "Ow", "\u1ECC\u0300w", "Bé", "\u1ECC\u0300p", } // monthNamesZulu list the month names in the Zulu. monthNamesZulu = []string{"Januwari", "Febhuwari", "Mashi", "Ephreli", "Meyi", "Juni", "Julayi", "Agasti", "Septemba", "Okthoba", "Novemba", "Disemba"} // monthNamesZuluAbbr list the month name abbreviations in the Zulu. monthNamesZuluAbbr = []string{"Jan", "Feb", "Mas", "Eph", "Mey", "Jun", "Jul", "Agas", "Sep", "Okt", "Nov", "Dis"} // weekdayNamesAfrikaans list the weekday name in the Afrikaans. weekdayNamesAfrikaans = []string{"Sondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrydag", "Saterdag"} // weekdayNamesAfrikaansAbbr list the weekday name abbreviations in the // Afrikaans. weekdayNamesAfrikaansAbbr = []string{"So.", "Ma.", "Di.", "Wo.", "Do.", "Vr.", "Sa."} // weekdayNamesAlbanian list the weekday name in the Albanian. weekdayNamesAlbanian = []string{"e diel", "e hënë", "e martë", "e mërkurë", "e enjte", "e premte", "e shtunë"} // weekdayNamesAlbanianAbbr list the weekday name abbreviations in the // Albanian. weekdayNamesAlbanianAbbr = []string{"die", "hën", "mar", "mër", "enj", "pre", "sht"} // weekdayNamesAlsatian list the weekday name in the Alsatian. weekdayNamesAlsatian = []string{"Sunntig", "Määntig", "Ziischtig", "Mittwuch", "Dunschtig", "Friitig", "Samschtig"} // weekdayNamesAlsatianAbbr list the weekday name abbreviations in the // Alsatian. weekdayNamesAlsatianAbbr = []string{"Su.", "Mä.", "Zi.", "Mi.", "Du.", "Fr.", "Sa."} // weekdayNamesAlsatianFrance list the weekday name in the Alsatian France. weekdayNamesAlsatianFrance = []string{"Sundi", "Manti", "Zischti", "Mettwuch", "Dunnerschti", "Friti", "Sàmschti"} // weekdayNamesAlsatianFranceAbbr list the weekday name abbreviations in the // Alsatian France. weekdayNamesAlsatianFranceAbbr = []string{"Su.", "Ma.", "Zi.", "Me.", "Du.", "Fr.", "Sà."} // weekdayNamesAmharic list the weekday name in the Amharic. weekdayNamesAmharic = []string{ "\u12A5\u1211\u12F5", "\u1230\u129E", "\u121B\u12AD\u1230\u129E", "\u1228\u1261\u12D5", "\u1210\u1219\u1235", "\u12D3\u122D\u1265", "\u1245\u12F3\u121C", } // weekdayNamesAmharicAbbr list the weekday name abbreviations in the // Amharic. weekdayNamesAmharicAbbr = []string{ "\u12A5\u1211\u12F5", "\u1230\u129E", "\u121B\u12AD\u1230", "\u1228\u1261\u12D5", "\u1210\u1219\u1235", "\u12D3\u122D\u1265", "\u1245\u12F3\u121C", } // weekdayNamesArabic list the weekday name in the Arabic. weekdayNamesArabic = []string{ "\u0627\u0644\u0623\u062D\u062F", "\u0627\u0644\u0625\u062B\u0646\u064A\u0646", "\u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621", "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621", "\u0627\u0644\u062E\u0645\u064A\u0633", "\u0627\u0644\u062C\u0645\u0639\u0629", "\u0627\u0644\u0633\u0628\u062A", } // weekdayNamesArabicAbbr list the weekday name abbreviations in the Arabic. weekdayNamesArabicAbbr = []string{ "\u0627\u0644\u0623\u062D\u062F", "\u0627\u0644\u0625\u062B\u0646\u064A\u0646", "\u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621", "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621", "\u0627\u0644\u062E\u0645\u064A\u0633", "\u0627\u0644\u062C\u0645\u0639\u0629", "\u0627\u0644\u0633\u0628\u062A", } // weekdayNamesArmenian list the weekday name in the Armenian. weekdayNamesArmenian = []string{ "\u053F\u056B\u0580\u0561\u056F\u056B", "\u0535\u0580\u056F\u0578\u0582\u0577\u0561\u0562\u0569\u056B", "\u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B", "\u0549\u0578\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B", "\u0540\u056B\u0576\u0563\u0577\u0561\u0562\u0569\u056B", "\u0548\u0582\u0580\u0562\u0561\u0569", "\u0547\u0561\u0562\u0561\u0569", } // weekdayNamesArmenianAbbr list the weekday name abbreviations in the // Armenian. weekdayNamesArmenianAbbr = []string{ "\u053F\u056B\u0580", "\u0535\u0580\u056F", "\u0535\u0580\u0584", "\u0549\u0580\u0584", "\u0540\u0576\u0563", "\u0548\u0582\u0580", "\u0547\u0562\u0569", } // weekdayNamesAssamese list the weekday name in the Assamese. weekdayNamesAssamese = []string{ "\u09F0\u09AC\u09BF\u09AC\u09BE\u09F0", "\u09B8\u09CB\u09AE\u09AC\u09BE\u09F0", "\u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0", "\u09AC\u09C1\u09A7\u09AC\u09BE\u09F0", "\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF\u09AC\u09BE\u09F0", "\u09B6\u09C1\u0995\u09CD\u09B0\u09AC\u09BE\u09F0", "\u09B6\u09A8\u09BF\u09AC\u09BE\u09F0", } // weekdayNamesAssameseAbbr list the weekday name abbreviations in the // Assamese. weekdayNamesAssameseAbbr = []string{ "\u09F0\u09AC\u09BF.", "\u09B8\u09CB\u09AE.", "\u09AE\u0999\u09CD\u0997\u09B2.", "\u09AC\u09C1\u09A7.", "\u09AC\u09C3\u09B9.", "\u09B6\u09C1\u0995\u09CD\u09B0.", "\u09B6\u09A8\u09BF.", } // weekdayNamesAzerbaijaniCyrillic list the weekday name in the Azerbaijani // (Cyrillic). weekdayNamesAzerbaijaniCyrillic = []string{ "\u0431\u0430\u0437\u0430\u0440", "\u0431\u0430\u0437\u0430\u0440 \u0435\u0440\u0442\u04D9\u0441\u0438", "\u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B", "\u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9", "\u04B9\u04AF\u043C\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B", "\u04B9\u04AF\u043C\u04D9", "\u0448\u04D9\u043D\u0431\u04D9", } // weekdayNamesAzerbaijaniCyrillicAbbr list the weekday name abbreviations // in the Azerbaijani (Cyrillic). weekdayNamesAzerbaijaniCyrillicAbbr = []string{"\u0411", "\u0411\u0435", "\u0427\u0430", "\u0427", "\u04B8\u0430", "\u04B8", "\u0428"} // weekdayNamesAzerbaijani list the weekday name in the Azerbaijani. weekdayNamesAzerbaijani = []string{ "bazar", "bazarçert\u0259si", "ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131", "ç\u0259r\u015F\u0259nb\u0259", "cüm\u0259 ax\u015Fam\u0131", "cüm\u0259", "\u015F\u0259nb\u0259", } // weekdayNamesAzerbaijaniAbbr list the weekday name abbreviations in the // Azerbaijani. weekdayNamesAzerbaijaniAbbr = []string{"B.", "B.E.", "Ç.A.", "Ç.", "C.A.", "C.", "\u015E."} // weekdayNamesBangla list the weekday name in the Bangla. weekdayNamesBangla = []string{ "\u09B0\u09AC\u09BF\u09AC\u09BE\u09B0", "\u09B8\u09CB\u09AE\u09AC\u09BE\u09B0", "\u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0", "\u09AC\u09C1\u09A7\u09AC\u09BE\u09B0", "\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF\u09AC\u09BE\u09B0", "\u09B6\u09C1\u0995\u09CD\u09B0\u09AC\u09BE\u09B0", "\u09B6\u09A8\u09BF\u09AC\u09BE\u09B0", } // weekdayNamesBanglaAbbr list the weekday name abbreviations in the Bangla. weekdayNamesBanglaAbbr = []string{ "\u09B0\u09AC\u09BF.", "\u09B8\u09CB\u09AE.", "\u09AE\u0999\u09CD\u0997\u09B2.", "\u09AC\u09C1\u09A7.", "\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF.", "\u09B6\u09C1\u0995\u09CD\u09B0.", "\u09B6\u09A8\u09BF.", } // weekdayNamesBashkir list the weekday name in the Bashkir. weekdayNamesBashkir = []string{ "\u0419\u04D9\u043A\u0448\u04D9\u043C\u0431\u0435", "\u0414\u04AF\u0448\u04D9\u043C\u0431\u0435", "\u0428\u0438\u0448\u04D9\u043C\u0431\u0435", "\u0428\u0430\u0440\u0448\u0430\u043C\u0431\u044B", "\u041A\u0435\u0441\u0430\u0499\u043D\u0430", "\u0419\u043E\u043C\u0430", "\u0428\u04D9\u043C\u0431\u0435", } // weekdayNamesBashkirAbbr list the weekday name abbreviations in the // Bashkir. weekdayNamesBashkirAbbr = []string{ "\u0419\u0448", "\u0414\u0448", "\u0428\u0448", "\u0428\u0440", "\u041A\u0441", "\u0419\u043C", "\u0428\u0431", } // weekdayNamesBasque list the weekday name in the Basque. weekdayNamesBasque = []string{"igandea", "astelehena", "asteartea", "asteazkena", "osteguna", "ostirala", "larunbata"} // weekdayNamesBasqueAbbr list the weekday name abbreviations in the Basque. weekdayNamesBasqueAbbr = []string{"ig.", "al.", "ar.", "az.", "og.", "or.", "lr."} // weekdayNamesBelarusian list the weekday name in the Belarusian. weekdayNamesBelarusian = []string{ "\u043D\u044F\u0434\u0437\u0435\u043B\u044F", "\u043F\u0430\u043D\u044F\u0434\u0437\u0435\u043B\u0430\u043A", "\u0430\u045E\u0442\u043E\u0440\u0430\u043A", "\u0441\u0435\u0440\u0430\u0434\u0430", "\u0447\u0430\u0446\u0432\u0435\u0440", "\u043F\u044F\u0442\u043D\u0456\u0446\u0430", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesBelarusianAbbr list the weekday name abbreviations in the // Belarusian. weekdayNamesBelarusianAbbr = []string{ "\u043D\u0434", "\u043F\u043D", "\u0430\u045E\u0442", "\u0441\u0440", "\u0447\u0446", "\u043F\u0442", "\u0441\u0431", } // weekdayNamesBosnianCyrillic list the weekday name in the Bosnian // (Cyrillic). weekdayNamesBosnianCyrillic = []string{ "\u043D\u0435\u0434\u0458\u0435\u0459\u0430", "\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A", "\u0443\u0442\u043E\u0440\u0430\u043A", "\u0441\u0440\u0438\u0458\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A", "\u043F\u0435\u0442\u0430\u043A", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesBosnianCyrillicAbbr list the weekday name abbreviations in // the Bosnian (Cyrillic). weekdayNamesBosnianCyrillicAbbr = []string{ "\u043D\u0435\u0434", "\u043F\u043E\u043D", "\u0443\u0442\u043E", "\u0441\u0440\u0435", "\u0447\u0435\u0442", "\u043F\u0435\u0442", "\u0441\u0443\u0431", } // weekdayNamesBosnian list the weekday name in the Bosnian. weekdayNamesBosnian = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"} // weekdayNamesBosnianAbbr list the weekday name abbreviations in the // Bosnian. weekdayNamesBosnianAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"} // weekdayNamesBreton list the weekday name in the Breton. weekdayNamesBreton = []string{"Sul", "Lun", "Meurzh", "Merc'her", "Yaou", "Gwener", "Sadorn"} // weekdayNamesBretonAbbr list the weekday name abbreviations in the Breton. weekdayNamesBretonAbbr = []string{"Sul", "Lun", "Meu.", "Mer.", "Yaou", "Gwe.", "Sad."} // weekdayNamesBulgarian list the weekday name in the Bulgarian. weekdayNamesBulgarian = []string{ "\u043D\u0435\u0434\u0435\u043B\u044F", "\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u043D\u0438\u043A", "\u0432\u0442\u043E\u0440\u043D\u0438\u043A", "\u0441\u0440\u044F\u0434\u0430", "\u0447\u0435\u0442\u0432\u044A\u0440\u0442\u044A\u043A", "\u043F\u0435\u0442\u044A\u043A", "\u0441\u044A\u0431\u043E\u0442\u0430", } // weekdayNamesBulgarianAbbr list the weekday name abbreviations in the // Bulgarian. weekdayNamesBulgarianAbbr = []string{ "\u043D\u0435\u0434", "\u043F\u043E\u043D", "\u0432\u0442", "\u0441\u0440", "\u0447\u0435\u0442\u0432", "\u043F\u0435\u0442", "\u0441\u044A\u0431", } // weekdayNamesBurmese list the weekday name in the Burmese. weekdayNamesBurmese = []string{ "\u1010\u1014\u1004\u103A\u1039\u1002\u1014\u103D\u1031", "\u1010\u1014\u1004\u103A\u1039\u101C\u102C", "\u1021\u1004\u103A\u1039\u1002\u102B", "\u1017\u102F\u1012\u1039\u1013\u101F\u1030\u1038", "\u1000\u103C\u102C\u101E\u1015\u1010\u1031\u1038", "\u101E\u1031\u102C\u1000\u103C\u102C", "\u1005\u1014\u1031", } // weekdayNamesCentralKurdish list the weekday name in the Central Kurdish. weekdayNamesCentralKurdish = []string{ "\u06CC\u06D5\u06A9\u0634\u06D5\u0645\u0645\u06D5", "\u062F\u0648\u0648\u0634\u06D5\u0645\u0645\u06D5", "\u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5", "\u0686\u0648\u0627\u0631\u0634\u06D5\u0645\u0645\u06D5", "\u067E\u06CE\u0646\u062C\u0634\u06D5\u0645\u0645\u06D5", "\u06BE\u06D5\u06CC\u0646\u06CC", "\u0634\u06D5\u0645\u0645\u06D5", } // weekdayNamesCherokee list the weekday name in the Cherokee. weekdayNamesCherokee = []string{ "\u13A4\u13BE\u13D9\u13D3\u13C6\u13CD\u13AC", "\u13A4\u13BE\u13D9\u13D3\u13C9\u13C5\u13AF", "\u13D4\u13B5\u13C1\u13A2\u13A6", "\u13E6\u13A2\u13C1\u13A2\u13A6", "\u13C5\u13A9\u13C1\u13A2\u13A6", "\u13E7\u13BE\u13A9\u13B6\u13CD\u13D7", "\u13A4\u13BE\u13D9\u13D3\u13C8\u13D5\u13BE", } // weekdayNamesCherokeeAbbr list the weekday name abbreviations in the // Cherokee. weekdayNamesCherokeeAbbr = []string{ "\u13C6\u13CD\u13AC", "\u13C9\u13C5\u13AF", "\u13D4\u13B5\u13C1", "\u13E6\u13A2\u13C1", "\u13C5\u13A9\u13C1", "\u13E7\u13BE\u13A9", "\u13C8\u13D5\u13BE", } // weekdayNamesCorsican list the weekday name in the Corsican. weekdayNamesCorsican = []string{"dumenica", "luni", "marti", "mercuri", "ghjovi", "venneri", "sabbatu"} // weekdayNamesCorsicanAbbr list the weekday name abbreviations in the Corsican. weekdayNamesCorsicanAbbr = []string{"dum.", "lun.", "mar.", "mer.", "ghj.", "ven.", "sab."} // weekdayNamesCroatian list the weekday name in the Croatian. weekdayNamesCroatian = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"} // weekdayNamesCroatianAbbr list the weekday name abbreviations in the Croatian. weekdayNamesCroatianAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"} // weekdayNamesCzech list the weekday name in the Czech. weekdayNamesCzech = []string{"neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota"} // weekdayNamesCzechAbbr list the weekday name abbreviations in the Czech. weekdayNamesCzechAbbr = []string{"ne", "po", "út", "st", "čt", "pá", "so"} // weekdayNamesDanish list the weekday name in the Danish. weekdayNamesDanish = []string{"søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"} // weekdayNamesDanishAbbr list the weekday name abbreviations in the Danish. weekdayNamesDanishAbbr = []string{"sø", "ma", "ti", "on", "to", "fr", "lø"} // weekdayNamesDari list the weekday name in the Dari. weekdayNamesDari = []string{ "\u06CC\u06A9\u0634\u0646\u0628\u0647", "\u062F\u0648\u0634\u0646\u0628\u0647", "\u0633\u0647\u200C \u0634\u0646\u0628\u0647", "\u0686\u0647\u0627\u0631 \u0634\u0646\u0628\u0647", "\u067E\u0646\u062C\u0634\u0646\u0628\u0647", "\u062C\u0645\u0639\u0647", "\u0634\u0646\u0628\u0647", } // weekdayNamesDivehi list the weekday name in the Divehi. weekdayNamesDivehi = []string{ "\u0787\u07A7\u078B\u07A9\u0787\u07B0\u078C\u07A6", "\u0780\u07AF\u0789\u07A6", "\u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6", "\u0784\u07AA\u078B\u07A6", "\u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8", "\u0780\u07AA\u0786\u07AA\u0783\u07AA", "\u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA", } // weekdayNamesDutch list the weekday name in the Dutch. weekdayNamesDutch = []string{"zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"} // weekdayNamesDutchAbbr list the weekday name abbreviations in the Dutch. weekdayNamesDutchAbbr = []string{"zo", "ma", "di", "wo", "do", "vr", "za"} // weekdayNamesDzongkha list the weekday name in the Dzongkha. weekdayNamesDzongkha = []string{ "\u0F42\u0F5F\u0F60\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0B", "\u0F42\u0F5F\u0F60\u0F0B\u0F49\u0F72\u0F0B\u0F58\u0F0B", } // weekdayNamesDzongkhaAbbr list the weekday name abbreviations in the // Dzongkha. weekdayNamesDzongkhaAbbr = []string{ "\u0F5F\u0FB3\u0F0B", "\u0F58\u0F72\u0F62\u0F0B", "\u0F63\u0FB7\u0F42\u0F0B", "\u0F55\u0F74\u0F62\u0F0B", "\u0F66\u0F44\u0F66\u0F0B", "\u0F66\u0FA4\u0F7A\u0F53\u0F0B", "\u0F49\u0F72\u0F0B", } // weekdayNamesChinese list the weekday name in the Chinese. weekdayNamesChinese = []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"} // weekdayNamesChineseAbbr list the weekday name abbreviations in the // Chinese. weekdayNamesChineseAbbr = []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"} // weekdayNamesChineseAbbr list the weekday name abbreviations in the // Chinese. weekdayNamesChineseAbbr2 = []string{"週日", "週一", "週二", "週三", "週四", "週五", "週六"} // weekdayNamesEnglish list the weekday name in the English. weekdayNamesEnglish = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} // weekdayNamesEnglishAbbr list the weekday name abbreviations in the // English. weekdayNamesEnglishAbbr = []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} // weekdayNamesEstonian list the weekday name in the Estonian. weekdayNamesEstonian = []string{"pühapäev", "esmaspäev", "teisipäev", "kolmapäev", "neljapäev", "reede", "laupäev"} // weekdayNamesEstonianAbbr list the weekday name abbreviations in the // Estonian. weekdayNamesEstonianAbbr = []string{"P", "E", "T", "K", "N", "R", "L"} // weekdayNamesFaroese list the weekday name in the Faroese. weekdayNamesFaroese = []string{"sunnudagur", "mánadagur", "týsdagur", "mikudagur", "hósdagur", "fríggjadagur", "leygardagur"} // weekdayNamesFaroeseAbbr list the weekday name abbreviations in the // Faroese. weekdayNamesFaroeseAbbr = []string{"sun.", "mán.", "týs.", "mik.", "hós.", "frí.", "ley."} // weekdayNamesFilipino list the weekday name in the Filipino. weekdayNamesFilipino = []string{"Linggo", "Lunes", "Martes", "Miyerkules", "Huwebes", "Biyernes", "Sabado"} // weekdayNamesFilipinoAbbr list the weekday name abbreviations in the // Filipino. weekdayNamesFilipinoAbbr = []string{"Lin", "Lun", "Mar", "Miy", "Huw", "Biy", "Sab"} // weekdayNamesFinnish list the weekday name in the Finnish weekdayNamesFinnish = []string{"sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai"} // weekdayNamesFinnishAbbr list the weekday name abbreviations in the // Finnish. weekdayNamesFinnishAbbr = []string{"su", "ma", "ti", "ke", "to", "pe", "la"} // weekdayNamesFrench list the weekday name in the French. weekdayNamesFrench = []string{"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"} // weekdayNamesFrenchAbbr list the weekday name abbreviations in the French. weekdayNamesFrenchAbbr = []string{"dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."} // weekdayNamesFrisian list the weekday name in the Frisian. weekdayNamesFrisian = []string{"snein", "moandei", "tiisdei", "woansdei", "tongersdei", "freed", "sneon"} // weekdayNamesFrisianAbbr list the weekday name abbreviations in the // Frisian. weekdayNamesFrisianAbbr = []string{"sni", "moa", "tii", "woa", "ton", "fre", "sno"} // weekdayNamesFulah list the weekday name in the Fulah. weekdayNamesFulah = []string{"dewo", "aaɓnde", "mawbaare", "njeslaare", "naasaande", "mawnde", "hoore-biir"} // weekdayNamesFulahAbbr list the weekday name abbreviations in the Fulah weekdayNamesFulahAbbr = []string{"dew", "aaɓ", "maw", "nje", "naa", "mwd", "hbi"} // weekdayNamesNigeria list the weekday name in the Nigeria weekdayNamesNigeria = []string{"alete", "altine", "talaata", "alarba", "alkamiisa", "aljumaa", "asete"} // weekdayNamesNigeriaAbbr list the weekday name abbreviations in the // Nigeria. weekdayNamesNigeriaAbbr = []string{"alet", "alt.", "tal.", "alar.", "alk.", "alj.", "aset"} // weekdayNamesGalician list the weekday name in the Galician. weekdayNamesGalician = []string{"domingo", "luns", "martes", "mércores", "xoves", "venres", "sábado"} // weekdayNamesGalicianAbbr list the weekday name abbreviations in the // Galician. weekdayNamesGalicianAbbr = []string{"dom.", "luns", "mar.", "mér.", "xov.", "ven.", "sáb."} // weekdayNamesGeorgian list the weekday name in the Georgian. weekdayNamesGeorgian = []string{ "\u10D9\u10D5\u10D8\u10E0\u10D0", "\u10DD\u10E0\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8", "\u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8", "\u10DD\u10D7\u10EE\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8", "\u10EE\u10E3\u10D7\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8", "\u10DE\u10D0\u10E0\u10D0\u10E1\u10D9\u10D4\u10D5\u10D8", "\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8", } // weekdayNamesGeorgianAbbr list the weekday name abbreviations in the // Georgian. weekdayNamesGeorgianAbbr = []string{ "\u10D9\u10D5.", "\u10DD\u10E0\u10E8.", "\u10E1\u10D0\u10DB\u10E8.", "\u10DD\u10D7\u10EE\u10E8.", "\u10EE\u10E3\u10D7\u10E8.", "\u10DE\u10D0\u10E0.", "\u10E8\u10D0\u10D1.", } // weekdayNamesGerman list the weekday name in the German. weekdayNamesGerman = []string{"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"} // weekdayNamesGermanAbbr list the weekday name abbreviations in the German. weekdayNamesGermanAbbr = []string{"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"} // weekdayNamesGreek list the weekday name in the Greek. weekdayNamesGreek = []string{ "\u039A\u03C5\u03C1\u03B9\u03B1\u03BA\u03AE", "\u0394\u03B5\u03C5\u03C4\u03AD\u03C1\u03B1", "\u03A4\u03C1\u03AF\u03C4\u03B7", "\u03A4\u03B5\u03C4\u03AC\u03C1\u03C4\u03B7", "\u03A0\u03AD\u03BC\u03C0\u03C4\u03B7", "\u03A0\u03B1\u03C1\u03B1\u03C3\u03BA\u03B5\u03C5\u03AE", "\u03A3\u03AC\u03B2\u03B2\u03B1\u03C4\u03BF", } // weekdayNamesGreekAbbr list the weekday name abbreviations in the Greek. weekdayNamesGreekAbbr = []string{ "\u039A\u03C5\u03C1", "\u0394\u03B5\u03C5", "\u03A4\u03C1\u03B9", "\u03A4\u03B5\u03C4", "\u03A0\u03B5\u03BC", "\u03A0\u03B1\u03C1", "\u03A3\u03B1\u03B2", } // weekdayNamesGreenlandic list the weekday name in the Greenlandic. weekdayNamesGreenlandic = []string{"sapaat", "ataasinngorneq", "marlunngorneq", "pingasunngorneq", "sisamanngorneq", "tallimanngorneq", "arfininngorneq"} // weekdayNamesGreenlandicAbbr list the weekday name abbreviations in the // Greenlandic. weekdayNamesGreenlandicAbbr = []string{"sap.", "at.", "marl.", "ping.", "sis.", "tall.", "arf."} // weekdayNamesGuarani list the weekday name in the Guarani. weekdayNamesGuarani = []string{"arate\u0129", "arakõi", "araapy", "ararundy", "arapo", "arapote\u0129", "arapokõi"} // weekdayNamesGuaraniAbbr list the weekday name abbreviations in the // Guarani. weekdayNamesGuaraniAbbr = []string{"te\u0129", "kõi", "apy", "ndy", "po", "ote\u0129", "okõi"} // weekdayNamesGujarati list the weekday name in the Gujarati. weekdayNamesGujarati = []string{ "\u0AB0\u0AB5\u0ABF\u0AB5\u0ABE\u0AB0", "\u0AB8\u0ACB\u0AAE\u0AB5\u0ABE\u0AB0", "\u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0", "\u0AAC\u0AC1\u0AA7\u0AB5\u0ABE\u0AB0", "\u0A97\u0AC1\u0AB0\u0AC1\u0AB5\u0ABE\u0AB0", "\u0AB6\u0AC1\u0A95\u0ACD\u0AB0\u0AB5\u0ABE\u0AB0", "\u0AB6\u0AA8\u0ABF\u0AB5\u0ABE\u0AB0", } // weekdayNamesGujaratiAbbr list the weekday name abbreviations in the // Gujarati. weekdayNamesGujaratiAbbr = []string{ "\u0AB0\u0AB5\u0ABF", "\u0AB8\u0ACB\u0AAE", "\u0AAE\u0A82\u0A97\u0AB3", "\u0AAC\u0AC1\u0AA7", "\u0A97\u0AC1\u0AB0\u0AC1", "\u0AB6\u0AC1\u0A95\u0ACD\u0AB0", "\u0AB6\u0AA8\u0ABF", } // weekdayNamesHausa list the weekday name in the Hausa. weekdayNamesHausa = []string{"Lahadi", "Litinin", "Talata", "Laraba", "Alhamis", "Jumma\u02bca", "Asabar"} // weekdayNamesHausaAbbr list the weekday name abbreviations in the Hausa. weekdayNamesHausaAbbr = []string{"Lah", "Lit", "Tal", "Lar", "Alh", "Jum", "Asa"} // weekdayNamesHawaiian list the weekday name in the Hawaiian. weekdayNamesHawaiian = []string{"Lāpule", "Po\u02bbakahi", "Po\u02bbalua", "Po\u02bbakolu", "Po\u02bbahā", "Po\u02bbalima", "Po\u02bbaono"} // weekdayNamesHawaiianAbbr list the weekday name abbreviations in the // Hawaiian. weekdayNamesHawaiianAbbr = []string{"LP", "P1", "P2", "P3", "P4", "P5", "P6"} // weekdayNamesHebrew list the weekday name in the Hebrew. weekdayNamesHebrew = []string{ "\u05D9\u05D5\u05DD \u05E8\u05D0\u05E9\u05D5\u05DF", "\u05D9\u05D5\u05DD \u05E9\u05E0\u05D9", "\u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9", "\u05D9\u05D5\u05DD \u05E8\u05D1\u05D9\u05E2\u05D9", "\u05D9\u05D5\u05DD \u05D7\u05DE\u05D9\u05E9\u05D9", "\u05D9\u05D5\u05DD \u05E9\u05D9\u05E9\u05D9", "\u05E9\u05D1\u05EA", } // weekdayNamesHebrewAbbr list the weekday name abbreviations in the Hebrew. weekdayNamesHebrewAbbr = []string{ "\u05D9\u05D5\u05DD \u05D0", "\u05D9\u05D5\u05DD \u05D1", "\u05D9\u05D5\u05DD \u05D2", "\u05D9\u05D5\u05DD \u05D3", "\u05D9\u05D5\u05DD \u05D4", "\u05D9\u05D5\u05DD \u05D5", "\u05E9\u05D1\u05EA", } // weekdayNamesHindi list the weekday name in the Hindi. weekdayNamesHindi = []string{ "\u0930\u0935\u093F\u0935\u093E\u0930", "\u0938\u094B\u092E\u0935\u093E\u0930", "\u092E\u0902\u0917\u0932\u0935\u093E\u0930", "\u092C\u0941\u0927\u0935\u093E\u0930", "\u0917\u0941\u0930\u0941\u0935\u093E\u0930", "\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930", "\u0936\u0928\u093F\u0935\u093E\u0930", } // weekdayNamesHindiAbbr list the weekday name abbreviations in the Hindi. weekdayNamesHindiAbbr = []string{ "\u0930\u0935\u093F.", "\u0938\u094B\u092E.", "\u092E\u0902\u0917\u0932.", "\u092C\u0941\u0927.", "\u0917\u0941\u0930\u0941.", "\u0936\u0941\u0915\u094D\u0930.", "\u0936\u0928\u093F.", } // weekdayNamesHungarian list the weekday name in the Hungarian. weekdayNamesHungarian = []string{"vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat"} // weekdayNamesHungarianAbbr list the weekday name abbreviations in the // Hungarian. weekdayNamesHungarianAbbr = []string{"V", "H", "K", "Sze", "Cs", "P", "Szo"} // weekdayNamesIcelandic list the weekday name in the Icelandic. weekdayNamesIcelandic = []string{"sunnudagur", "mánudagur", "þriðjudagur", "miðvikudagur", "fimmtudagur", "föstudagur", "laugardagur"} // weekdayNamesIcelandicAbbr list the weekday name abbreviations in the // Icelandic. weekdayNamesIcelandicAbbr = []string{"sun.", "mán.", "þri.", "mið.", "fim.", "fös.", "lau."} // weekdayNamesIgbo list the weekday name in the Igbo. weekdayNamesIgbo = []string{"Ụbọchị Ụka", "Mọnde", "Tiuzdee", "Wenezdee", "Tọọzdee", "Fraịdee", "Satọdee"} // weekdayNamesIgboAbbr list the weekday name abbreviations in the Igbo. weekdayNamesIgboAbbr = []string{"Ụka", "Mọn", "Tiu", "Wen", "Tọọ", "Fraị", "Satọdee"} // weekdayNamesIndonesian list the weekday name in the Indonesian. weekdayNamesIndonesian = []string{"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"} // weekdayNamesIndonesianAbbr list the weekday name abbreviations in the // Indonesian. weekdayNamesIndonesianAbbr = []string{"Mgg", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"} // weekdayNamesInuktitut list the weekday name in the Inuktitut. weekdayNamesInuktitut = []string{"Naattiinguja", "Naggajjau", "Aippiq", "Pingatsiq", "Sitammiq", "Tallirmiq", "Sivataarvik"} // weekdayNamesInuktitutAbbr list the weekday name abbreviations in the // Inuktitut. weekdayNamesInuktitutAbbr = []string{"Nat", "Nag", "Aip", "Pi", "Sit", "Tal", "Siv"} // weekdayNamesSyllabics list the weekday name in the Syllabics. weekdayNamesSyllabics = []string{ "\u14C8\u1466\u144F\u1591\u152D", "\u14C7\u14A1\u1490\u153E\u152D\u1405", "\u140A\u1403\u1449\u1431\u1585", "\u1431\u1593\u1466\u14EF\u1585", "\u14EF\u1455\u14BB\u14A5\u1585", "\u1455\u14EA\u14D5\u1550\u14A5\u1585", "\u14EF\u1559\u1456\u1550\u1555\u1483", } // weekdayNamesSyllabicsAbbr list the weekday name abbreviations in the // Syllabics. weekdayNamesSyllabicsAbbr = []string{ "\u14C8\u1466\u144F", "\u14C7\u14A1\u1490", "\u140A\u1403\u1449\u1431", "\u1431\u1593\u1466\u14EF", "\u14EF\u1455", "\u1455\u14EA\u14D5", "\u14EF\u1559\u1456\u1550\u1555\u1483", } // weekdayNamesIrish list the weekday name in the Irish. weekdayNamesIrish = []string{"Dé Domhnaigh", "Dé Luain", "Dé Máirt", "Dé Céadaoin", "Déardaoin", "Dé hAoine", "Dé Sathairn"} // weekdayNamesIrishAbbr list the weekday name abbreviations in the Irish. weekdayNamesIrishAbbr = []string{"Domh", "Luan", "Máirt", "Céad", "Déar", "Aoine", "Sath"} // weekdayNamesItalian list the weekday name in the Italian. weekdayNamesItalian = []string{"domenica", "lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato"} // weekdayNamesItalianAbbr list the weekday name abbreviations in the // Italian. weekdayNamesItalianAbbr = []string{"dom", "lun", "mar", "mer", "gio", "ven", "sab"} // weekdayNamesJapanese list the weekday name in the Japanese. weekdayNamesJapanese = []string{"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"} // weekdayNamesJapaneseAbbr list the weekday name abbreviations in the // Japanese. weekdayNamesJapaneseAbbr = []string{"日", "月", "火", "水", "木", "金", "土"} // weekdayNamesKannada list the weekday name in the Kannada. weekdayNamesKannada = []string{ "\u0CAD\u0CBE\u0CA8\u0CC1\u0CB5\u0CBE\u0CB0", "\u0CB8\u0CCB\u0CAE\u0CB5\u0CBE\u0CB0", "\u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0", "\u0CAC\u0CC1\u0CA7\u0CB5\u0CBE\u0CB0", "\u0C97\u0CC1\u0CB0\u0CC1\u0CB5\u0CBE\u0CB0", "\u0CB6\u0CC1\u0C95\u0CCD\u0CB0\u0CB5\u0CBE\u0CB0", "\u0CB6\u0CA8\u0CBF\u0CB5\u0CBE\u0CB0", } // weekdayNamesKannadaAbbr list the weekday name abbreviations in the // Kannada. weekdayNamesKannadaAbbr = []string{ "\u0CAD\u0CBE\u0CA8\u0CC1.", "\u0CB8\u0CCB\u0CAE.", "\u0CAE\u0C82\u0C97\u0CB3.", "\u0CAC\u0CC1\u0CA7.", "\u0C97\u0CC1\u0CB0\u0CC1.", "\u0CB6\u0CC1\u0C95\u0CCD\u0CB0.", "\u0CB6\u0CA8\u0CBF.", } // weekdayNamesKashmiri list the weekday name in the Kashmiri. weekdayNamesKashmiri = []string{ "\u0627\u064E\u062A\u06BE\u0648\u0627\u0631", "\u0698\u0654\u0646\u062F\u0631\u0655\u0631\u0648\u0627\u0631", "\u0628\u06C6\u0645\u0648\u0627\u0631", "\u0628\u0648\u062F\u0648\u0627\u0631", "\u0628\u0631\u0620\u0633\u0648\u0627\u0631", "\u062C\u064F\u0645\u06C1", "\u0628\u0679\u0648\u0627\u0631", } // weekdayNamesKashmiriAbbr list the weekday name abbreviations in the // Kashmiri. weekdayNamesKashmiriAbbr = []string{ "\u0622\u062A\u06BE\u0648\u0627\u0631", "\u0698\u0654\u0646\u062F\u0655\u0631\u0648\u0627\u0631", "\u0628\u06C6\u0645\u0648\u0627\u0631", "\u0628\u0648\u062F\u0648\u0627\u0631", "\u0628\u0631\u0620\u0633\u0648\u0627\u0631", "\u062C\u064F\u0645\u06C1", "\u0628\u0679\u0648\u0627\u0631", } // weekdayNamesKazakh list the weekday name in the Kazakh. weekdayNamesKazakh = []string{ "\u0436\u0435\u043A\u0441\u0435\u043D\u0431\u0456", "\u0434\u04AF\u0439\u0441\u0435\u043D\u0431\u0456", "\u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456", "\u0441\u04D9\u0440\u0441\u0435\u043D\u0431\u0456", "\u0431\u0435\u0439\u0441\u0435\u043D\u0431\u0456", "\u0436\u04B1\u043C\u0430", "\u0441\u0435\u043D\u0431\u0456", } // weekdayNamesKazakhAbbr list the weekday name abbreviations in the Kazakh. weekdayNamesKazakhAbbr = []string{ "\u0436\u0435\u043A", "\u0434\u04AF\u0439", "\u0441\u0435\u0439", "\u0441\u04D9\u0440", "\u0431\u0435\u0439", "\u0436\u04B1\u043C", "\u0441\u0435\u043D", } // weekdayNamesKhmer list the weekday name in the Khmer. weekdayNamesKhmer = []string{ "\u1790\u17D2\u1784\u17C3\u17A2\u17B6\u1791\u17B7\u178F\u17D2\u1799", "\u1790\u17D2\u1784\u17C3\u1785\u17D0\u1793\u17D2\u1791", "\u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A", "\u1790\u17D2\u1784\u17C3\u1796\u17BB\u1792", "\u1790\u17D2\u1784\u17C3\u1796\u17D2\u179A\u17A0\u179F\u17D2\u1794\u178F\u17B7\u17CD", "\u1790\u17D2\u1784\u17C3\u179F\u17BB\u1780\u17D2\u179A", "\u1790\u17D2\u1784\u17C3\u179F\u17C5\u179A\u17CD", } // weekdayNamesKhmerAbbr list the weekday name abbreviations in the Khmer. weekdayNamesKhmerAbbr = []string{ "\u17A2\u17B6\u1791\u17B7.", "\u1785.", "\u17A2.", "\u1796\u17BB", "\u1796\u17D2\u179A\u17A0.", "\u179F\u17BB.", "\u179F.", } // weekdayNamesKiche list the weekday name in the Kiche. weekdayNamesKiche = []string{"juq'ij", "kaq'ij", "oxq'ij", "kajq'ij", "joq'ij", "waqq'ij", "wuqq'ij"} // weekdayNamesKicheAbbr list the weekday name abbreviations in the Kiche. weekdayNamesKicheAbbr = []string{"juq'", "kaq'", "oxq'", "kajq'", "joq'", "waqq'", "wuqq'"} // weekdayNamesKinyarwanda list the weekday name in the Kinyarwanda. weekdayNamesKinyarwanda = []string{"Ku cyumweru", "Ku wa mbere", "Ku wa kabiri", "Ku wa gatatu", "Ku wa kane", "Ku wa gatanu", "Ku wa gatandatu"} // weekdayNamesKinyarwandaAbbr list the weekday name abbreviations in the // Kinyarwanda. weekdayNamesKinyarwandaAbbr = []string{"cyu.", "mbe.", "kab.", "gat.", "kan.", "gnu.", "gat."} // weekdayNamesKiswahili list the weekday name in the Kiswahili. weekdayNamesKiswahili = []string{"Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi"} // weekdayNamesKiswahiliAbbr list the weekday name abbreviations in the // Kiswahili. weekdayNamesKiswahiliAbbr = []string{"Jpl", "Jtt", "Jnn", "Jtn", "Alh", "Ijm", "Jms"} // weekdayNamesKonkani list the weekday name in the Konkani. weekdayNamesKonkani = []string{ "\u0906\u092F\u0924\u093E\u0930", "\u0938\u094B\u092E\u093E\u0930", "\u092E\u0902\u0917\u0933\u093E\u0930", "\u092C\u0941\u0927\u0935\u093E\u0930", "\u092C\u093F\u0930\u0947\u0938\u094D\u0924\u093E\u0930", "\u0938\u0941\u0915\u094D\u0930\u093E\u0930", "\u0936\u0947\u0928\u0935\u093E\u0930", } // weekdayNamesKonkaniAbbr list the weekday name abbreviations in the // Konkani. weekdayNamesKonkaniAbbr = []string{ "\u0906\u092F.", "\u0938\u094B\u092E.", "\u092E\u0902\u0917\u0933.", "\u092C\u0941\u0927.", "\u092C\u093F\u0930\u0947.", "\u0938\u0941\u0915\u094D\u0930.", "\u0936\u0947\u0928.", } // weekdayNamesKorean list the weekday name in the Korean. weekdayNamesKorean = []string{"일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"} // weekdayNamesKoreanAbbr list the weekday name abbreviations in the Korean. weekdayNamesKoreanAbbr = []string{"일", "월", "화", "수", "목", "금", "토"} // weekdayNamesKyrgyz list the weekday name in the Kyrgyz. weekdayNamesKyrgyz = []string{ "\u0436\u0435\u043A\u0448\u0435\u043C\u0431\u0438", "\u0434\u04AF\u0439\u0448\u04E9\u043C\u0431\u04AF", "\u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438", "\u0448\u0430\u0440\u0448\u0435\u043C\u0431\u0438", "\u0431\u0435\u0439\u0448\u0435\u043C\u0431\u0438", "\u0436\u0443\u043C\u0430", "\u0438\u0448\u0435\u043C\u0431\u0438", } // weekdayNamesKyrgyzAbbr list the weekday name abbreviations in the Kyrgyz. weekdayNamesKyrgyzAbbr = []string{ "\u0436\u0435\u043A.", "\u0434\u04AF\u0439.", "\u0448\u0435\u0439\u0448.", "\u0448\u0430\u0440\u0448.", "\u0431\u0435\u0439\u0448.", "\u0436\u0443\u043C\u0430", "\u0438\u0448\u043C.", } // weekdayNamesLao list the weekday name in the Lao. weekdayNamesLao = []string{ "\u0EA7\u0EB1\u0E99\u0EAD\u0EB2\u0E97\u0EB4\u0E94", "\u0EA7\u0EB1\u0E99\u0E88\u0EB1\u0E99", "\u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99", "\u0EA7\u0EB1\u0E99\u0E9E\u0EB8\u0E94", "\u0EA7\u0EB1\u0E99\u0E9E\u0EB0\u0EAB\u0EB1\u0E94", "\u0EA7\u0EB1\u0E99\u0EAA\u0EB8\u0E81", "\u0EA7\u0EB1\u0E99\u0EC0\u0EAA\u0EBB\u0EB2", } // weekdayNamesLaoAbbr list the weekday name abbreviations in the Lao. weekdayNamesLaoAbbr = []string{ "\u0EAD\u0EB2\u0E97\u0EB4\u0E94", "\u0E88\u0EB1\u0E99", "\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99", "\u0E9E\u0EB8\u0E94", "\u0E9E\u0EB0\u0EAB\u0EB1\u0E94", "\u0EAA\u0EB8\u0E81", "\u0EC0\u0EAA\u0EBB\u0EB2", } // weekdayNamesLatin list the weekday name in the Latin. weekdayNamesLatin = []string{"Solis", "Lunae", "Martis", "Mercurii", "Jovis", "Veneris", "Saturni"} // weekdayNamesLatinAbbr list the weekday name abbreviations in the Latin. weekdayNamesLatinAbbr = []string{"Sol", "Lun", "Mar", "Mer", "Jov", "Ven", "Sat"} // weekdayNamesLatvian list the weekday name in the Latvian. weekdayNamesLatvian = []string{"svētdiena", "pirmdiena", "otrdiena", "trešdiena", "ceturtdiena", "piektdiena", "sestdiena"} // weekdayNamesLatvianAbbr list the weekday name abbreviations in the // Latvian. weekdayNamesLatvianAbbr = []string{"svētd.", "pirmd.", "otrd.", "trešd.", "ceturtd.", "piektd.", "sestd."} // weekdayNamesLithuanian list the weekday name in the Lithuanian. weekdayNamesLithuanian = []string{"sekmadienis", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis"} // weekdayNamesLithuanianAbbr list the weekday name abbreviations in the // Lithuanian. weekdayNamesLithuanianAbbr = []string{"sk", "pr", "an", "tr", "kt", "pn", "št"} // weekdayNamesLowerSorbian list the weekday name in the Lower Sorbian. weekdayNamesLowerSorbian = []string{"njeźela", "ponjeźele", "wałtora", "srjoda", "stwórtk", "pětk", "sobota"} // weekdayNamesLowerSorbianAbbr list the weekday name abbreviations in the // Lower Sorbian. weekdayNamesLowerSorbianAbbr = []string{"nje", "pon", "wa\u0142", "srj", "stw", "p\u011Bt", "sob"} // weekdayNamesLuxembourgish list the weekday name in the Luxembourgish. weekdayNamesLuxembourgish = []string{"Sonndeg", "Méindeg", "Dënschdeg", "Mëttwoch", "Donneschdeg", "Freideg", "Samschdeg"} // weekdayNamesLuxembourgishAbbr list the weekday name abbreviations in the // Luxembourgish. weekdayNamesLuxembourgishAbbr = []string{"Son", "Méi", "Dën", "Mët", "Don", "Fre", "Sam"} // weekdayNamesMacedonian list the weekday name in the Macedonian. weekdayNamesMacedonian = []string{ "\u043D\u0435\u0434\u0435\u043B\u0430", "\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u043D\u0438\u043A", "\u0432\u0442\u043E\u0440\u043D\u0438\u043A", "\u0441\u0440\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0440\u0442\u043E\u043A", "\u043F\u0435\u0442\u043E\u043A", "\u0441\u0430\u0431\u043E\u0442\u0430", } // weekdayNamesMacedonianAbbr list the weekday name abbreviations in the // Macedonian. weekdayNamesMacedonianAbbr = []string{ "\u043D\u0435\u0434.", "\u043F\u043E\u043D.", "\u0432\u0442.", "\u0441\u0440\u0435.", "\u0447\u0435\u0442.", "\u043F\u0435\u0442.", "\u0441\u0430\u0431.", } // weekdayNamesMalay list the weekday name in the Malay. weekdayNamesMalay = []string{"Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu"} // weekdayNamesMalayAbbr list the weekday name abbreviations in the Lower // Sorbian. weekdayNamesMalayAbbr = []string{"Ahd", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab"} // weekdayNamesMalayalam list the weekday name in the Malayalam. weekdayNamesMalayalam = []string{ "\u0D1E\u0D3E\u0D2F\u0D31\u0D3E\u0D34\u0D4D\u200C\u0D1A", "\u0D24\u0D3F\u0D19\u0D4D\u0D15\u0D33\u0D3E\u0D34\u0D4D\u200C\u0D1A", "\u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A", "\u0D2C\u0D41\u0D27\u0D28\u0D3E\u0D34\u0D4D\u200C\u0D1A", "\u0D35\u0D4D\u0D2F\u0D3E\u0D34\u0D3E\u0D34\u0D4D\u200C\u0D1A", "\u0D35\u0D46\u0D33\u0D4D\u0D33\u0D3F\u0D2F\u0D3E\u0D34\u0D4D\u200C\u0D1A", "\u0D36\u0D28\u0D3F\u0D2F\u0D3E\u0D34\u0D4D\u200C\u0D1A", } // weekdayNamesMalayalamAbbr list the weekday name abbreviations in the // Malayalam. weekdayNamesMalayalamAbbr = []string{ "\u0D1E\u0D3E\u0D2F\u0D7C", "\u0D24\u0D3F\u0D19\u0D4D\u0D15\u0D7E", "\u0D1A\u0D4A\u0D35\u0D4D\u0D35", "\u0D2C\u0D41\u0D27\u0D7B", "\u0D35\u0D4D\u0D2F\u0D3E\u0D34\u0D02", "\u0D35\u0D46\u0D33\u0D4D\u0D33\u0D3F", "\u0D36\u0D28\u0D3F", } // weekdayNamesMaltese list the weekday name in the Maltese. weekdayNamesMaltese = []string{"Il-\u0126add", "It-Tnejn", "It-Tlieta", "L-Erbg\u0127a", "Il-\u0126amis", "Il-\u0120img\u0127a", "Is-Sibt"} // weekdayNamesMalteseAbbr list the weekday name abbreviations in the // Maltese. weekdayNamesMalteseAbbr = []string{"\u0126ad", "Tne", "Tli", "Erb", "\u0126am", "\u0120im", "Sib"} // weekdayNamesMaori list the weekday name in the Maori. weekdayNamesMaori = []string{"Rātapu", "Rāhina", "Rātū", "Rāapa", "Rāpare", "Rāmere", "Rāhoroi"} // weekdayNamesMaoriAbbr list the weekday name abbreviations in the Maori. weekdayNamesMaoriAbbr = []string{"Ta", "Hi", "Tū", "Apa", "Pa", "Me", "Ho"} // weekdayNamesMapudungun list the weekday name in the Mapudungun. weekdayNamesMapudungun = []string{"Kiñe Ante", "Epu Ante", "Kila Ante", "Meli Ante", "Kechu Ante", "Cayu Ante", "Regle Ante"} // weekdayNamesMapudungunAbbr list the weekday name abbreviations in the // Mapudungun. weekdayNamesMapudungunAbbr = []string{"Kiñe", "Epu", "Kila", "Meli", "Kechu", "Cayu", "Regle"} // weekdayNamesMarathi list the weekday name in the Marathi. weekdayNamesMarathi = []string{ "\u0930\u0935\u093F\u0935\u093E\u0930", "\u0938\u094B\u092E\u0935\u093E\u0930", "\u092E\u0902\u0917\u0933\u0935\u093E\u0930", "\u092C\u0941\u0927\u0935\u093E\u0930", "\u0917\u0941\u0930\u0941\u0935\u093E\u0930", "\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930", "\u0936\u0928\u093F\u0935\u093E\u0930", } // weekdayNamesMarathiAbbr list the weekday name abbreviations in the // Marathi. weekdayNamesMarathiAbbr = []string{ "\u0930\u0935\u093F.", "\u0938\u094B\u092E.", "\u092E\u0902\u0917\u0933.", "\u092C\u0941\u0927.", "\u0917\u0941\u0930\u0941.", "\u0936\u0941\u0915\u094D\u0930.", "\u0936\u0928\u093F.", } // weekdayNamesMohawk list the weekday name in the Mohawk. weekdayNamesMohawk = []string{"Awentatokentì:ke", "Awentataón'ke", "Ratironhia'kehronòn:ke", "Soséhne", "Okaristiiáhne", "Ronwaia'tanentaktonhne", "Entákta"} // weekdayNamesMongolian list the weekday name in the Mongolian. weekdayNamesMongolian = []string{ "\u043D\u044F\u043C", "\u0434\u0430\u0432\u0430\u0430", "\u043C\u044F\u0433\u043C\u0430\u0440", "\u043B\u0445\u0430\u0433\u0432\u0430", "\u043F\u04AF\u0440\u044D\u0432", "\u0431\u0430\u0430\u0441\u0430\u043D", "\u0431\u044F\u043C\u0431\u0430", } // weekdayNamesMongolianAbbr list the weekday name abbreviations in the // Mongolian. weekdayNamesMongolianAbbr = []string{ "\u041D\u044F", "\u0414\u0430", "\u041C\u044F", "\u041B\u0445", "\u041F\u04AF", "\u0411\u0430", "\u0411\u044F", } // weekdayNamesMongolianCyrlAbbr list the weekday name abbreviations in the // Mongolian (Cyrillic). weekdayNamesMongolianCyrlAbbr = []string{ "\u041D\u044F", "\u0414\u0430", "\u041C\u044F", "\u041B\u0445\u0430", "\u041F\u04AF", "\u0411\u0430", "\u0411\u044F", } // weekdayNamesTraditionalMongolian list the weekday name abbreviations in // the Traditional Mongolian. weekdayNamesTraditionalMongolian = []string{ "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1821\u1833\u1826\u1837", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1828\u1822\u182D\u1821\u1828", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u182C\u1823\u1836\u1820\u1837", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u182D\u1824\u1837\u182A\u1820\u1828", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1832\u1820\u182A\u1824\u1828", "\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828", } // weekdayNamesTraditionalMongolianMN list the weekday name abbreviations in // the Traditional Mongolian MN. weekdayNamesTraditionalMongolianMN = []string{ "\u1828\u1822\u182E\u180E\u1820", "\u1833\u1820\u1838\u1820", "\u182E\u1822\u182D\u182E\u1820\u1837", "\u1840\u1820\u182D\u182A\u1820", "\u182B\u1826\u1837\u182A\u1826", "\u182A\u1820\u1830\u1820\u1829", "\u182A\u1822\u182E\u182A\u1820", } // weekdayNamesNepali list the weekday name in the Nepali. weekdayNamesNepali = []string{ "\u0906\u0907\u0924\u0935\u093E\u0930", "\u0938\u094B\u092E\u0935\u093E\u0930", "\u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930", "\u092C\u0941\u0927\u0935\u093E\u0930", "\u092C\u093F\u0939\u0940\u0935\u093E\u0930", "\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930", "\u0936\u0928\u093F\u0935\u093E\u0930", } // weekdayNamesNepaliAbbr list the weekday name abbreviations in the Nepali. weekdayNamesNepaliAbbr = []string{ "\u0906\u0907\u0924", "\u0938\u094B\u092E", "\u092E\u0919\u094D\u0917\u0932", "\u092C\u0941\u0927", "\u092C\u093F\u0939\u0940", "\u0936\u0941\u0915\u094D\u0930", "\u0936\u0928\u093F", } // weekdayNamesNepaliIN list the weekday name in the Nepali India. weekdayNamesNepaliIN = []string{ "\u0906\u0907\u0924\u092C\u093E\u0930", "\u0938\u094B\u092E\u092C\u093E\u0930", "\u092E\u0919\u094D\u0917\u0932\u092C\u093E\u0930", "\u092C\u0941\u0927\u092C\u093E\u0930", "\u092C\u093F\u0939\u093F\u092C\u093E\u0930", "\u0936\u0941\u0915\u094D\u0930\u092C\u093E\u0930", "\u0936\u0928\u093F\u092C\u093E\u0930", } // weekdayNamesNepaliINAbbr list the weekday name abbreviations in the // Nepali India. weekdayNamesNepaliINAbbr = []string{ "\u0906\u0907\u0924", "\u0938\u094B\u092E", "\u092E\u0919\u094D\u0917\u0932", "\u092C\u0941\u0927", "\u092C\u093F\u0939\u093F", "\u0936\u0941\u0915\u094D\u0930", "\u0936\u0928\u093F", } // weekdayNamesNorwegian list the weekday name in the Norwegian. weekdayNamesNorwegian = []string{"søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"} // weekdayNamesNorwegianAbbr list the weekday name abbreviations in the // Norwegian. weekdayNamesNorwegianAbbr = []string{"søn.", "man.", "tir.", "ons.", "tor.", "fre.", "lør."} // weekdayNamesNorwegianNOAbbr list the weekday name abbreviations in the // Norwegian Norway. weekdayNamesNorwegianNOAbbr = []string{"søn", "man", "tir", "ons", "tor", "fre", "lør"} // weekdayNamesNorwegianNynorsk list the weekday name abbreviations in the // Norwegian Nynorsk. weekdayNamesNorwegianNynorsk = []string{"søndag", "måndag", "tysdag", "onsdag", "torsdag", "fredag", "laurdag"} // weekdayNamesNorwegianNynorskAbbr list the weekday name abbreviations in // the Norwegian Nynorsk. weekdayNamesNorwegianNynorskAbbr = []string{"søn", "mån", "tys", "ons", "tor", "fre", "lau"} // weekdayNamesOccitan list the weekday name abbreviations in the Occitan. weekdayNamesOccitan = []string{"dimenge", "diluns", "dimarts", "dimècres", "dijòus", "divendres", "dissabte"} // weekdayNamesOccitanAbbr list the weekday name abbreviations in the // Occitan. weekdayNamesOccitanAbbr = []string{"dg.", "dl.", "dma.", "dmc.", "dj.", "dv.", "ds."} // weekdayNamesOdia list the weekday name in the Odia. weekdayNamesOdia = []string{ "\u0B30\u0B2C\u0B3F\u0B2C\u0B3E\u0B30", "\u0B38\u0B4B\u0B2E\u0B2C\u0B3E\u0B30", "\u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30", "\u0B2C\u0B41\u0B27\u0B2C\u0B3E\u0B30", "\u0B17\u0B41\u0B30\u0B41\u0B2C\u0B3E\u0B30", "\u0B36\u0B41\u0B15\u0B4D\u0B30\u0B2C\u0B3E\u0B30", "\u0B36\u0B28\u0B3F\u0B2C\u0B3E\u0B30", } // weekdayNamesOdiaAbbr list the weekday name abbreviations in the Odia. weekdayNamesOdiaAbbr = []string{ "\u0B30\u0B2C\u0B3F.", "\u0B38\u0B4B\u0B2E.", "\u0B2E\u0B19\u0B4D\u0B17\u0B33.", "\u0B2C\u0B41\u0B27.", "\u0B17\u0B41\u0B30\u0B41.", "\u0B36\u0B41\u0B15\u0B4D\u0B30.", "\u0B36\u0B28\u0B3F.", } // weekdayNamesOromo list the weekday name abbreviations in the Oromo. weekdayNamesOromo = []string{"Dilbata", "Wiixata", "Qibxata", "Roobii", "Kamiisa", "Jimaata", "Sanbata"} // weekdayNamesOromoAbbr list the weekday name abbreviations in the Oromo. weekdayNamesOromoAbbr = []string{"Dil", "Wix", "Qib", "Rob", "Kam", "Jim", "San"} // weekdayNamesPashto list the weekday name in the Pashto. weekdayNamesPashto = []string{ "\u064A\u0648\u0646\u06CD", "\u062F\u0648\u0646\u06CD", "\u062F\u0631\u06D0\u0646\u06CD", "\u0685\u0644\u0631\u0646\u06CD", "\u067E\u064A\u0646\u0681\u0646\u06CD", "\u062C\u0645\u0639\u0647", "\u0627\u0648\u0646\u06CD", } // weekdayNamesPersian list the weekday name in the Persian. weekdayNamesPersian = []string{ "\u064A\u0643\u0634\u0646\u0628\u0647", "\u062F\u0648\u0634\u0646\u0628\u0647", "\u0633\u0647 \u0634\u0646\u0628\u0647", "\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647", "\u067E\u0646\u062C\u0634\u0646\u0628\u0647", "\u062C\u0645\u0639\u0647", "\u0634\u0646\u0628\u0647", } // weekdayNamesPolish list the weekday name abbreviations in the Polish. weekdayNamesPolish = []string{"niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"} // weekdayNamesPolishAbbr list the weekday name abbreviations in the Polish. weekdayNamesPolishAbbr = []string{"niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob."} // weekdayNamesPortuguese list the weekday name abbreviations in the // Portuguese. weekdayNamesPortuguese = []string{"domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado"} // weekdayNamesPortugueseAbbr list the weekday name abbreviations in the // Portuguese. weekdayNamesPortugueseAbbr = []string{"dom", "seg", "ter", "qua", "qui", "sex", "sáb"} // weekdayNamesPunjabi list the weekday name in the Punjabi. weekdayNamesPunjabi = []string{ "\u0A10\u0A24\u0A35\u0A3E\u0A30", "\u0A38\u0A4B\u0A2E\u0A35\u0A3E\u0A30", "\u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30", "\u0A2C\u0A41\u0A71\u0A27\u0A35\u0A3E\u0A30", "\u0A35\u0A40\u0A30\u0A35\u0A3E\u0A30", "\u0A38\u0A3C\u0A41\u0A71\u0A15\u0A30\u0A35\u0A3E\u0A30", "\u0A38\u0A3C\u0A28\u0A3F\u0A71\u0A1A\u0A30\u0A35\u0A3E\u0A30", } // weekdayNamesPunjabiAbbr list the weekday name abbreviations in the // Punjabi. weekdayNamesPunjabiAbbr = []string{ "\u0A10\u0A24.", "\u0A38\u0A4B\u0A2E.", "\u0A2E\u0A70\u0A17\u0A32.", "\u0A2C\u0A41\u0A71\u0A27.", "\u0A35\u0A40\u0A30.", "\u0A38\u0A3C\u0A41\u0A15\u0A30.", "\u0A38\u0A3C\u0A28\u0A3F\u0A71\u0A1A\u0A30.", } // weekdayNamesPunjabiArab list the weekday name in the Punjabi Arab. weekdayNamesPunjabiArab = []string{ "\u067E\u064A\u0631", "\u0645\u0646\u06AF\u0644", "\u0628\u062F\u06BE", "\u062C\u0645\u0639\u0631\u0627\u062A", "\u062C\u0645\u0639\u0647", "\u0647\u0641\u062A\u0647", "\u0627\u062A\u0648\u0627\u0631", } // weekdayNamesQuechua list the weekday name abbreviations in the Quechua. weekdayNamesQuechua = []string{"intichaw", "killachaw", "atipachaw", "quyllurchaw", "Ch' askachaw", "Illapachaw", "k'uychichaw"} // weekdayNamesQuechuaAbbr list the weekday name abbreviations in the Quechua. weekdayNamesQuechuaAbbr = []string{"int", "kil", "ati", "quy", "Ch'", "Ill", "k'u"} // weekdayNamesQuechuaEcuador list the weekday name abbreviations in the // Quechua Ecuador. weekdayNamesQuechuaEcuador = []string{"inti", "awaki", "wanra", "chillay", "kullka", "chaska", "wakma"} // weekdayNamesQuechuaEcuadorAbbr list the weekday name abbreviations in the // Quechua Ecuador. weekdayNamesQuechuaEcuadorAbbr = []string{"int", "awk", "wan", "chy", "kuk", "cha", "wak"} // weekdayNamesQuechuaPeru list the weekday name abbreviations in the // Quechua Peru. weekdayNamesQuechuaPeru = []string{"Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"} // weekdayNamesQuechuaPeruAbbr list the weekday name abbreviations in the // Quechua Peru. weekdayNamesQuechuaPeruAbbr = []string{"Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sab"} // weekdayNamesRomanian list the weekday name abbreviations in the Romanian. weekdayNamesRomanian = []string{"duminică", "luni", "marți", "miercuri", "joi", "vineri", "sâmbătă"} // weekdayNamesRomanianAbbr list the weekday name abbreviations in the // Romanian. weekdayNamesRomanianAbbr = []string{"dum.", "lun.", "mar.", "mie.", "joi", "vin.", "sâm."} // weekdayNamesRomanianMoldovaAbbr list the weekday name abbreviations in // the Romanian Moldova. weekdayNamesRomanianMoldovaAbbr = []string{"Du", "Lu", "Mar", "Mie", "Jo", "Vi", "Sâ"} // weekdayNamesRomansh list the weekday name abbreviations in the Romansh. weekdayNamesRomansh = []string{"dumengia", "glindesdi", "mardi", "mesemna", "gievgia", "venderdi", "sonda"} // weekdayNamesRomanshAbbr list the weekday name abbreviations in the // Romansh. weekdayNamesRomanshAbbr = []string{"du", "gli", "ma", "me", "gie", "ve", "so"} // weekdayNamesRussian list the weekday name abbreviations in the Russian. weekdayNamesRussian = []string{ "\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435", "\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A", "\u0432\u0442\u043E\u0440\u043D\u0438\u043A", "\u0441\u0440\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0435\u0440\u0433", "\u043F\u044F\u0442\u043D\u0438\u0446\u0430", "\u0441\u0443\u0431\u0431\u043E\u0442\u0430", } // weekdayNamesRussianAbbr list the weekday name abbreviations in the // Russian. weekdayNamesRussianAbbr = []string{ "\u0412\u0441", "\u041F\u043D", "\u0412\u0442", "\u0421\u0440", "\u0427\u0442", "\u041F\u0442", "\u0421\u0431", } // weekdayNamesSakha list the weekday name abbreviations in the Sakha. weekdayNamesSakha = []string{ "\u04E8\u0440\u04E9\u0431\u04AF\u043B", "\u044D\u043D\u0438\u0434\u0438\u044D\u043D\u043D\u044C\u0438\u043A", "\u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A", "\u0421\u044D\u0440\u044D\u0434\u044D\u044D", "\u0427\u044D\u043F\u043F\u0438\u044D\u0440", "\u0411\u044D\u044D\u0442\u0438\u043D\u0441\u044D", "\u0421\u0443\u0431\u0443\u043E\u0442\u0430", } // weekdayNamesSakhaAbbr list the weekday name abbreviations in the Sakha. weekdayNamesSakhaAbbr = []string{ "\u04E8\u0440", "\u0431\u043D", "\u043E\u043F", "\u0441\u044D", "\u0447\u043F", "\u0431\u044D", "\u0441\u0431", } // weekdayNamesSami list the weekday name abbreviations in the Sami. weekdayNamesSami = []string{"pasepeivi", "vuossargâ", "majebargâ", "koskokko", "tuorâstâh", "vástuppeivi", "lávurdâh"} // weekdayNamesSamiAbbr list the weekday name abbreviations in the Sami. weekdayNamesSamiAbbr = []string{"pas", "vuo", "maj", "kos", "tuo", "vás", "láv"} // weekdayNamesSamiSamiLule list the weekday name abbreviations in the Sami // (SamiLule). weekdayNamesSamiSamiLule = []string{"ájllek", "mánnodahka", "dijstahka", "gasskavahkko", "duorastahka", "bierjjedahka", "lávvodahka"} // weekdayNamesSamiSamiLuleAbbr list the weekday name abbreviations in the // Sami (SamiLule). weekdayNamesSamiSamiLuleAbbr = []string{"ájl", "mán", "dis", "gas", "duor", "bier", "láv"} // weekdayNamesSamiSweden list the weekday name abbreviations in the Sami // (Lule) Sweden. weekdayNamesSamiSweden = []string{"sådnåbiejvve", "mánnodahka", "dijstahka", "gasskavahkko", "duorastahka", "bierjjedahka", "lávvodahka"} // weekdayNamesSamiSwedenAbbr list the weekday name abbreviations in the // Sami (Lule) Sweden. weekdayNamesSamiSwedenAbbr = []string{"såd", "mán", "dis", "gas", "duor", "bier", "láv"} // weekdayNamesSamiNorthern list the weekday name abbreviations in the Sami // (Northern). weekdayNamesSamiNorthern = []string{"sotnabeaivi", "vuossárga", "maŋŋebárga", "gaskavahkku", "duorasdat ", "bearjadat", "lávvardat"} // weekdayNamesSamiNorthernFIAbbr list the weekday name abbreviations in the // Sami (Northern). weekdayNamesSamiNorthernAbbr = []string{"sotn", "vuos", "maŋ", "gask", "duor", "bear", "láv"} // weekdayNamesSamiNorthernFI list the weekday name abbreviations in the // Sami (Northern) Finland. weekdayNamesSamiNorthernFI = []string{"sotnabeaivi", "vuossárga", "maŋŋebárga", "gaskavahkku", "duorastat", "bearjadat", "lávvardat"} // weekdayNamesSamiNorthernFIAbbr list the weekday name abbreviations in the // Sami (Northern) Finland. weekdayNamesSamiNorthernFIAbbr = []string{"so", "má", "di", "ga", "du", "be", "lá"} // weekdayNamesSamiNorthernSE list the weekday name abbreviations in the // Sami (Northern) Sweden. weekdayNamesSamiNorthernSE = []string{"sotnabeaivi", "mánnodat", "disdat", "gaskavahkku", "duorastat", "bearjadat", "lávvardat"} // weekdayNamesSamiNorthernSEAbbr list the weekday name abbreviations in the // Sami (Northern) Sweden. weekdayNamesSamiNorthernSEAbbr = []string{"sotn", "mán", "dis", "gask", "duor", "bear", "láv"} // weekdayNamesSamiSkolt list the weekday name abbreviations in the Sami // (Skolt). weekdayNamesSamiSkolt = []string{"pâ\u00B4sspei\u00B4vv", "vuõssargg", "mââibargg", "seärad", "neljdpei\u00B4vv", "piâtnâc", "sue\u00B4vet"} // weekdayNamesSamiSkoltAbbr list the weekday name abbreviations in the Sami // (Skolt). weekdayNamesSamiSkoltAbbr = []string{"pâ", "vu", "mâ", "se", "ne", "pi", "su"} // weekdayNamesSamiSouthern list the weekday name abbreviations in the Sami // (Southern). weekdayNamesSamiSouthern = []string{"aejlege", "måanta", "dæjsta", "gaskevåhkoe", "duarsta", "bearjadahke", "laavvardahke"} // weekdayNamesSamiSouthernAbbr list the weekday name abbreviations in the // Sami (Southern). weekdayNamesSamiSouthernAbbr = []string{"aej", "måa", "dæj", "gask", "duar", "bearj", "laav"} // weekdayNamesSanskrit list the weekday name abbreviations in the Sanskrit. weekdayNamesSanskrit = []string{ "\u0930\u0935\u093F\u0935\u093E\u0938\u0930\u0903", "\u0938\u094B\u092E\u0935\u093E\u0938\u0930\u0903", "\u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903", "\u092C\u0941\u0927\u0935\u093E\u0938\u0930\u0903", "\u0917\u0941\u0930\u0941\u0935\u093E\u0938\u0930:", "\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0938\u0930\u0903", "\u0936\u0928\u093F\u0935\u093E\u0938\u0930\u0903", } // weekdayNamesSanskritAbbr list the weekday name abbreviations in the // Sanskrit. weekdayNamesSanskritAbbr = []string{ "\u0930\u0935\u093F", "\u0938\u094B\u092E", "\u092E\u0919\u094D\u0917", "\u092C\u0941\u0927", "\u0917\u0941\u0930\u0941", "\u0936\u0941\u0915\u094D\u0930", "\u0936\u0928\u093F", } // weekdayNamesGaelic list the weekday name abbreviations in the Gaelic. weekdayNamesGaelic = []string{"DiDòmhnaich", "DiLuain", "DiMàirt", "DiCiadain", "DiarDaoin", "DihAoine", "DiSathairne"} // weekdayNamesGaelicAbbr list the weekday name abbreviations in the Gaelic. weekdayNamesGaelicAbbr = []string{"DiD", "DiL", "DiM", "DiC", "Dia", "Dih", "DiS"} // weekdayNamesSerbian list the weekday name abbreviations in the Serbian. weekdayNamesSerbian = []string{ "\u043D\u0435\u0434\u0435\u0459\u0430", "\u043F\u043E\u043D\u0435\u0434\u0435\u0459\u0430\u043A", "\u0443\u0442\u043E\u0440\u0430\u043A", "\u0441\u0440\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A", "\u043F\u0435\u0442\u0430\u043A", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesSerbianAbbr list the weekday name abbreviations in the // Serbian. weekdayNamesSerbianAbbr = []string{ "\u043D\u0435\u0434.", "\u043F\u043E\u043D.", "\u0443\u0442.", "\u0441\u0440.", "\u0447\u0435\u0442.", "\u043F\u0435\u0442.", "\u0441\u0443\u0431.", } // weekdayNamesSerbianBA list the weekday name abbreviations in the Serbian // (Cyrillic) Bosnia and Herzegovina. weekdayNamesSerbianBA = []string{ "\u043D\u0435\u0434\u0458\u0435\u0459\u0430", "\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A", "\u0443\u0442\u043E\u0440\u0430\u043A", "\u0441\u0440\u0438\u0458\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A", "\u043F\u0435\u0442\u0430\u043A", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesSerbianBAAbbr list the weekday name abbreviations in the // Serbian (Cyrillic) Bosnia and Herzegovina. weekdayNamesSerbianBAAbbr = []string{ "\u043D\u0435\u0434", "\u043F\u043E\u043D", "\u0443\u0442\u043E", "\u0441\u0440\u0438", "\u0447\u0435\u0442", "\u043F\u0435\u0442", "\u0441\u0443\u0431", } // weekdayNamesSerbianLatin list the weekday name abbreviations in the // Serbian (Latin). weekdayNamesSerbianLatin = []string{"nedelja", "ponedeljak", "utorak", "sreda", "četvrtak", "petak", "subota"} // weekdayNamesSerbianLatinAbbr list the weekday name abbreviations in the // Serbian (Latin). weekdayNamesSerbianLatinAbbr = []string{"ned", "pon", "uto", "sre", "čet", "pet", "sub"} // weekdayNamesSerbianLatinBA list the weekday name abbreviations in the // Serbian (Latin) Bosnia and Herzegovina. weekdayNamesSerbianLatinBA = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"} // weekdayNamesSerbianLatinBAAbbr list the weekday name abbreviations in the // Serbian (Latin) Bosnia and Herzegovina. weekdayNamesSerbianLatinBAAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"} // weekdayNamesSerbianLatinCSAbbr list the weekday name abbreviations in the // Serbian (Latin) Serbia and Montenegro (Former). weekdayNamesSerbianLatinCSAbbr = []string{"ned.", "pon.", "uto.", "sre.", "čet.", "pet.", "sub."} // weekdayNamesSerbianLatinME list the weekday name abbreviations in the // Serbian (Latin) Montenegro. weekdayNamesSerbianLatinME = []string{"nedjelja", "ponedeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"} // weekdayNamesSerbianME list the weekday name abbreviations in the Serbian // (Cyrillic) Montenegro. weekdayNamesSerbianME = []string{ "\u043D\u0435\u0434\u0435\u0459\u0430", "\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A", "\u0443\u0442\u043E\u0440\u0430\u043A", "\u0441\u0440\u0438\u0458\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A", "\u043F\u0435\u0442\u0430\u043A", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesSesothoSaLeboa list the weekday name abbreviations in the // Sesotho sa Leboa. weekdayNamesSesothoSaLeboa = []string{"Lamorena", "Musopologo", "Labobedi", "Laboraro", "Labone", "Labohlano", "Mokibelo"} // weekdayNamesSesothoSaLeboaAbbr list the weekday name abbreviations in the // Sesotho sa Leboa. weekdayNamesSesothoSaLeboaAbbr = []string{"Lam", "Moš", "Lbb", "Lbr", "Lbn", "Lbh", "Mok"} // weekdayNamesSetswana list the weekday name abbreviations in the Setswana. weekdayNamesSetswana = []string{"Sontaga", "Mosopulogo", "Labobedi", "Laboraro", "Labone", "Labotlhano", "Matlhatso"} // weekdayNamesSetswanaAbbr list the weekday name abbreviations in the // Setswana. weekdayNamesSetswanaAbbr = []string{"Sont.", "Mos.", "Lab.", "Labr.", "Labn.", "Labt.", "Matlh."} // weekdayNamesSindhi list the weekday name abbreviations in the Sindhi. weekdayNamesSindhi = []string{ "\u0633\u0648\u0645\u0631", "\u0627\u06B1\u0627\u0631\u0648", "\u0627\u0631\u0628\u0639", "\u062E\u0645\u064A\u0633", "\u062C\u0645\u0639\u0648", "\u0687\u0646\u0687\u0631", "\u0622\u0686\u0631", } // weekdayNamesSindhiAbbr list the weekday name abbreviations in the Sindhi. weekdayNamesSindhiAbbr = []string{ "\u0633\u0648", "\u0627\u06B1", "\u0627\u0631", "\u062E\u0645", "\u062C\u0645\u0639\u0648", "\u0687\u0646", "\u0622\u0686", } // weekdayNamesSlovak list the weekday name abbreviations in the Slovak. weekdayNamesSlovak = []string{"nedeľa", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota"} // weekdayNamesSlovakAbbr list the weekday name abbreviations in the Slovak. weekdayNamesSlovakAbbr = []string{"ne", "po", "ut", "st", "št", "pi", "so"} // weekdayNamesSlovenian list the weekday name abbreviations in the // Slovenian. weekdayNamesSlovenian = []string{"nedelja", "ponedeljek", "torek", "sreda", "četrtek", "petek", "sobota"} // weekdayNamesSlovenianAbbr list the weekday name abbreviations in the // Slovenian. weekdayNamesSlovenianAbbr = []string{"ned.", "pon.", "tor.", "sre.", "čet.", "pet.", "sob."} // weekdayNamesSomali list the weekday name abbreviations in the Somali. weekdayNamesSomali = []string{"Axad", "Isniin", "Talaado", "Arbaco", "Khamiis", "Jimco", "Sabti"} // weekdayNamesSomaliAbbr list the weekday name abbreviations in the Somali. weekdayNamesSomaliAbbr = []string{"Axd", "Isn", "Tldo", "Arbc", "Khms", "Jmc", "Sbti"} // weekdayNamesSotho list the weekday name abbreviations in the Sotho. weekdayNamesSotho = []string{"Sontaha", "Mmantaha", "Labobedi", "Laboraru", "Labone", "Labohlane", "Moqebelo"} // weekdayNamesSothoAbbr list the weekday name abbreviations in the Sotho. weekdayNamesSothoAbbr = []string{"Son", "Mma", "Bed", "Rar", "Ne", "Hla", "Moq"} // weekdayNamesSpanish list the weekday name abbreviations in the Spanish. weekdayNamesSpanish = []string{"domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"} // weekdayNamesSpanishAbbr list the weekday name abbreviations in the // Spanish Argentina. weekdayNamesSpanishAbbr = []string{"do.", "lu.", "ma.", "mi.", "ju.", "vi.", "sá."} // weekdayNamesSpanishARAbbr list the weekday name abbreviations in the // Spanish Argentina. weekdayNamesSpanishARAbbr = []string{"dom.", "lun.", "mar.", "mié.", "jue.", "vie.", "sáb."} // weekdayNamesSpanishUSAbbr list the weekday name abbreviations in the // Spanish United States. weekdayNamesSpanishUSAbbr = []string{"dom", "lun", "mar", "mié", "jue", "vie", "sáb"} // weekdayNamesSwedish list the weekday name abbreviations in the Swedish. weekdayNamesSwedish = []string{"söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag"} // weekdayNamesSwedishAbbr list the weekday name abbreviations in the // Swedish Argentina. weekdayNamesSwedishAbbr = []string{"sön", "mån", "tis", "ons", "tor", "fre", "lör"} // weekdayNamesSyriac list the weekday name abbreviations in the Syriac. weekdayNamesSyriac = []string{ "\u071A\u0715 \u0712\u072B\u0712\u0710", "\u072C\u072A\u071D\u0722 \u0712\u072B\u0712\u0710", "\u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710", "\u0710\u072A\u0712\u0725\u0710 \u0712\u072B\u0712\u0710", "\u071A\u0721\u072B\u0710 \u0712\u072B\u0712\u0710", "\u0725\u072A\u0718\u0712\u072C\u0710", "\u072B\u0712\u072C\u0710", } // weekdayNamesSyriacAbbr list the weekday name abbreviations in the Syriac. weekdayNamesSyriacAbbr = []string{ "\u070F\u0710 \u070F\u0712\u072B", "\u070F\u0712 \u070F\u0712\u072B", "\u070F\u0713 \u070F\u0712\u072B", "\u070F\u0715 \u070F\u0712\u072B", "\u070F\u0717 \u070F\u0712\u072B", "\u070F\u0725\u072A\u0718\u0712", "\u070F\u072B\u0712", } // weekdayNamesTajik list the weekday name abbreviations in the Tajik. weekdayNamesTajik = []string{ "\u042F\u043A\u0448\u0430\u043D\u0431\u0435", "\u0434\u0443\u0448\u0430\u043D\u0431\u0435", "\u0441\u0435\u0448\u0430\u043D\u0431\u0435", "\u0447\u043E\u0440\u0448\u0430\u043D\u0431\u0435", "\u043F\u0430\u043D\u04B7\u0448\u0430\u043D\u0431\u0435", "\u04B7\u0443\u043C\u044A\u0430", "\u0448\u0430\u043D\u0431\u0435", } // weekdayNamesTajikAbbr list the weekday name abbreviations in the Tajik. weekdayNamesTajikAbbr = []string{ "\u043F\u043A\u0448", "\u0434\u0448\u0431", "\u0441\u0448\u0431", "\u0447\u0448\u0431", "\u043F\u0448\u0431", "\u04B7\u0443\u043C", "\u0448\u043D\u0431", } // weekdayNamesTamazight list the weekday name abbreviations in the // Tamazight. weekdayNamesTamazight = []string{"lh'ed", "letnayen", "ttlata", "larebâa", "lexmis", "ldjemâa", "ssebt"} // weekdayNamesTamazightAbbr list the weekday name abbreviations in the // Tamazight Argentina. weekdayNamesTamazightAbbr = []string{"lh'd", "let", "ttl", "lar", "lex", "ldj", "sse"} // weekdayNamesTamil list the weekday name abbreviations in the Tamil. weekdayNamesTamil = []string{ "\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BCD\u0BB1\u0BC1\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", "\u0B9A\u0BA9\u0BBF\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8", } // weekdayNamesTamilAbbr list the weekday name abbreviations in the Tamil. weekdayNamesTamilAbbr = []string{ "\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BC1", "\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD", "\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD", "\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD", "\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0BA9\u0BCD", "\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF", "\u0B9A\u0BA9\u0BBF", } // weekdayNamesTamilLK list the weekday name abbreviations in the Tamil Sri // Lanka. weekdayNamesTamilLK = []string{ "\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BC1", "\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD", "\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD", "\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD", "\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0BA9\u0BCD", "\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF", "\u0B9A\u0BA9\u0BBF", } // weekdayNamesTamilLKAbbr list the weekday name abbreviations in the Tamil // Sri Lanka. weekdayNamesTamilLKAbbr = []string{ "\u0B9E\u0BBE\u0BAF\u0BBF.", "\u0BA4\u0BBF\u0B99\u0BCD.", "\u0B9A\u0BC6\u0BB5\u0BCD.", "\u0BAA\u0BC1\u0BA4.", "\u0BB5\u0BBF\u0BAF\u0BBE.", "\u0BB5\u0BC6\u0BB3\u0BCD.", "\u0B9A\u0BA9\u0BBF", } // weekdayNamesTatar list the weekday name abbreviations in the Tatar. weekdayNamesTatar = []string{ "\u044F\u043A\u0448\u04D9\u043C\u0431\u0435", "\u0434\u04AF\u0448\u04D9\u043C\u0431\u0435", "\u0441\u0438\u0448\u04D9\u043C\u0431\u0435", "\u0447\u04D9\u0440\u0448\u04D9\u043C\u0431\u0435", "\u043F\u04D9\u043D\u0497\u0435\u0448\u04D9\u043C\u0431\u0435", "\u0497\u043E\u043C\u0433\u0430", "\u0448\u0438\u043C\u0431\u04D9", } // weekdayNamesTatarAbbr list the weekday name abbreviations in the Tatar. weekdayNamesTatarAbbr = []string{ "\u044F\u043A\u0448.", "\u0434\u04AF\u0448.", "\u0441\u0438\u0448.", "\u0447\u04D9\u0440\u0448.", "\u043F\u04D9\u043D\u0497.", "\u0497\u043E\u043C.", "\u0448\u0438\u043C.", } // weekdayNamesTelugu list the weekday name abbreviations in the Telugu. weekdayNamesTelugu = []string{ "\u0C06\u0C26\u0C3F\u0C35\u0C3E\u0C30\u0C02", "\u0C38\u0C4B\u0C2E\u0C35\u0C3E\u0C30\u0C02", "\u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02", "\u0C2C\u0C41\u0C27\u0C35\u0C3E\u0C30\u0C02", "\u0C17\u0C41\u0C30\u0C41\u0C35\u0C3E\u0C30\u0C02", "\u0C36\u0C41\u0C15\u0C4D\u0C30\u0C35\u0C3E\u0C30\u0C02", "\u0C36\u0C28\u0C3F\u0C35\u0C3E\u0C30\u0C02", } // weekdayNamesTeluguAbbr list the weekday name abbreviations in the Telugu. weekdayNamesTeluguAbbr = []string{ "\u0C06\u0C26\u0C3F", "\u0C38\u0C4B\u0C2E", "\u0C2E\u0C02\u0C17\u0C33", "\u0C2C\u0C41\u0C27", "\u0C17\u0C41\u0C30\u0C41", "\u0C36\u0C41\u0C15\u0C4D\u0C30", "\u0C36\u0C28\u0C3F", } // weekdayNamesThai list the weekday name abbreviations in the Thai. weekdayNamesThai = []string{ "\u0E2D\u0E32\u0E17\u0E34\u0E15\u0E22\u0E4C", "\u0E08\u0E31\u0E19\u0E17\u0E23\u0E4C", "\u0E2D\u0E31\u0E07\u0E04\u0E32\u0E23", "\u0E1E\u0E38\u0E18", "\u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35", "\u0E28\u0E38\u0E01\u0E23\u0E4C", "\u0E40\u0E2A\u0E32\u0E23\u0E4C", } // weekdayNamesThaiAbbr list the weekday name abbreviations in the Thai. weekdayNamesThaiAbbr = []string{ "\u0E2D\u0E32.", "\u0E08.", "\u0E2D.", "\u0E1E.", "\u0E1E\u0E24.", "\u0E28.", "\u0E2A.", } // weekdayNamesTibetan list the weekday name abbreviations in the Tibetan. weekdayNamesTibetan = []string{ "\u0F42\u0F5F\u0F60\u0F0B\u0F49\u0F72\u0F0B\u0F58\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0D", "\u0F42\u0F5F\u0F60\u0F0B\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D", } // weekdayNamesTibetanAbbr list the weekday name abbreviations in the // Tibetan. weekdayNamesTibetanAbbr = []string{ "\u0F49\u0F72\u0F0B\u0F58\u0F0D", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0D", "\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D", "\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0D", "\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D", "\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0D", "\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D", } // weekdayNamesTigrinya list the weekday name abbreviations in the Tigrinya. weekdayNamesTigrinya = []string{ "\u1230\u1295\u1260\u1275", "\u1230\u1291\u12ED", "\u1220\u1209\u1235", "\u1228\u1261\u12D5", "\u1283\u1219\u1235", "\u12D3\u122D\u1262", "\u1240\u12F3\u121D", } // weekdayNamesTigrinyaAbbr list the weekday name abbreviations in the // Tigrinya. weekdayNamesTigrinyaAbbr = []string{ "\u1230\u1295", "\u1230\u1291", "\u1230\u1209", "\u1228\u1261", "\u1213\u1219", "\u12D3\u122D", "\u1240\u12F3", } // weekdayNamesTsonga list the weekday name abbreviations in the Tsonga. weekdayNamesTsonga = []string{"Sonta", "Musumbhunuku", "Ravumbirhi", "Ravunharhu", "Ravumune", "Ravuntlhanu", "Mugqivela"} // weekdayNamesTsongaAbbr list the weekday name abbreviations in the Tsonga. weekdayNamesTsongaAbbr = []string{"Son", "Mus", "Bir", "Har", "Ne", "Tlh", "Mug"} // weekdayNamesTurkish list the weekday name abbreviations in the Turkish. weekdayNamesTurkish = []string{"Pazar", "Pazartesi", "Sal\u0131", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"} // weekdayNamesTurkishAbbr list the weekday name abbreviations in the // Turkish. weekdayNamesTurkishAbbr = []string{"Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"} // weekdayNamesTurkmen list the weekday name abbreviations in the Turkmen. weekdayNamesTurkmen = []string{"Ýekşenbe", "Duşenbe", "Sişenbe", "Çarşenbe", "Penşenbe", "Anna", "Şenbe"} // weekdayNamesTurkmenAbbr list the weekday name abbreviations in the // Turkmen. weekdayNamesTurkmenAbbr = []string{"Ýb", "Db", "Sb", "Çb", "Pb", "An", "Şb"} // weekdayNamesUkrainian list the weekday name abbreviations in the // Ukrainian. weekdayNamesUkrainian = []string{ "\u043D\u0435\u0434\u0456\u043B\u044F", "\u043F\u043E\u043D\u0435\u0434\u0456\u043B\u043E\u043A", "\u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A", "\u0441\u0435\u0440\u0435\u0434\u0430", "\u0447\u0435\u0442\u0432\u0435\u0440", "\u043F'\u044F\u0442\u043D\u0438\u0446\u044F", "\u0441\u0443\u0431\u043E\u0442\u0430", } // weekdayNamesUkrainianAbbr list the weekday name abbreviations in the // Ukrainian. weekdayNamesUkrainianAbbr = []string{ "\u041D\u0434", "\u041F\u043D", "\u0412\u0442", "\u0421\u0440", "\u0427\u0442", "\u041F\u0442", "\u0421\u0431", } // weekdayNamesSorbian list the weekday name abbreviations in the Sorbian. weekdayNamesSorbian = []string{"njedźela", "póndźela", "wutora", "srjeda", "štwórtk", "pjatk", "sobota"} // weekdayNamesSorbianAbbr list the weekday name abbreviations in the // Sorbian. weekdayNamesSorbianAbbr = []string{"nje", "pón", "wut", "srj", "štw", "pja", "sob"} // weekdayNamesUrdu list the weekday name abbreviations in the Urdu. weekdayNamesUrdu = []string{ "\u0627\u062A\u0648\u0627\u0631", "\u067E\u064A\u0631", "\u0645\u0646\u06AF\u0644", "\u0628\u062F\u06BE", "\u062C\u0645\u0639\u0631\u0627\u062A", "\u062C\u0645\u0639\u0647", "\u0647\u0641\u062A\u0647", } // weekdayNamesUrduIN list the weekday name abbreviations in the Urdu India. weekdayNamesUrduIN = []string{ "\u0627\u062A\u0648\u0627\u0631", "\u067E\u06CC\u0631", "\u0645\u0646\u06AF\u0644", "\u0628\u062F\u06BE", "\u062C\u0645\u0639\u0631\u0627\u062A", "\u062C\u0645\u0639\u06C1", "\u06C1\u0641\u062A\u06C1", } // weekdayNamesUyghur list the weekday name abbreviations in the Uyghur. weekdayNamesUyghur = []string{ "\u064A\u06D5\u0643\u0634\u06D5\u0646\u0628\u06D5", "\u062F\u06C8\u0634\u06D5\u0646\u0628\u06D5", "\u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5", "\u0686\u0627\u0631\u0634\u06D5\u0646\u0628\u06D5", "\u067E\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5", "\u062C\u06C8\u0645\u06D5", "\u0634\u06D5\u0646\u0628\u06D5", } // weekdayNamesUyghurAbbr list the weekday name abbreviations in the Uyghur. weekdayNamesUyghurAbbr = []string{ "\u064A\u06D5", "\u062F\u06C8", "\u0633\u06D5", "\u0686\u0627", "\u067E\u06D5", "\u062C\u06C8", "\u0634\u06D5", } // weekdayNamesUzbekCyrillic list the weekday name abbreviations in the // Uzbek Cyrillic. weekdayNamesUzbekCyrillic = []string{ "\u044F\u043A\u0448\u0430\u043D\u0431\u0430", "\u0434\u0443\u0448\u0430\u043D\u0431\u0430", "\u0441\u0435\u0448\u0430\u043D\u0431\u0430", "\u0447\u043E\u0440\u0448\u0430\u043D\u0431\u0430", "\u043F\u0430\u0439\u0448\u0430\u043D\u0431\u0430", "\u0436\u0443\u043C\u0430", "\u0448\u0430\u043D\u0431\u0430", } // weekdayNamesUzbekCyrillicAbbr list the weekday name abbreviations in the // Uzbek Cyrillic. weekdayNamesUzbekCyrillicAbbr = []string{ "\u044F\u043A\u0448", "\u0434\u0443\u0448", "\u0441\u0435\u0448", "\u0447\u043E\u0440", "\u043F\u0430\u0439", "\u0436\u0443\u043C", "\u0448\u0430\u043D", } // weekdayNamesUzbek list the weekday name abbreviations in the Uzbek. weekdayNamesUzbek = []string{"yakshanba", "dushanba", "seshanba", "chorshanba", "payshanba", "juma", "shanba"} // weekdayNamesUzbekAbbr list the weekday name abbreviations in the Uzbek. weekdayNamesUzbekAbbr = []string{"Yak", "Dush", "Sesh", "Chor", "Pay", "Jum", "Shan"} // weekdayNamesValencian list the weekday name abbreviations in the // Valencian. weekdayNamesValencian = []string{"diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"} // weekdayNamesValencianAbbr list the weekday name abbreviations in the // Valencian. weekdayNamesValencianAbbr = []string{"dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds."} // weekdayNamesVenda list the weekday name abbreviations in the Venda. weekdayNamesVenda = []string{"Swondaha", "Musumbuluwo", "Ḽavhuvhili", "Ḽavhuraru", "Ḽavhuṋa", "Ḽavhuṱanu", "Mugivhela"} // weekdayNamesVendaAbbr list the weekday name abbreviations in the Venda. weekdayNamesVendaAbbr = []string{"Swo", "Mus", "Vhi", "Rar", "Ṋa", "Ṱan", "Mug"} // weekdayNamesVietnamese list the weekday name abbreviations in the // Vietnamese. weekdayNamesVietnamese = []string{"Chu\u0309 Nhâ\u0323t", "Th\u01B0\u0301 Hai", "Th\u01B0\u0301 Ba", "Th\u01B0\u0301 T\u01B0", "Th\u01B0\u0301 N\u0103m", "Th\u01B0\u0301 Sa\u0301u", "Th\u01B0\u0301 Ba\u0309y"} // weekdayNamesVietnameseAbbr list the weekday name abbreviations in the // Vietnamese. weekdayNamesVietnameseAbbr = []string{"CN", "T2", "T3", "T4", "T5", "T6", "T7"} // weekdayNamesWelsh list the weekday name abbreviations in the Welsh. weekdayNamesWelsh = []string{"Dydd Sul", "Dydd Llun", "Dydd Mawrth", "Dydd Mercher", "Dydd Iau", "Dydd Gwener", "Dydd Sadwrn"} // weekdayNamesWelshAbbr list the weekday name abbreviations in the Welsh. weekdayNamesWelshAbbr = []string{"Sul", "Llun", "Maw", "Mer", "Iau", "Gwe", "Sad"} // weekdayNamesWolof list the weekday name abbreviations in the Wolof. weekdayNamesWolof = []string{"Dibéer", "Altine", "Talaata", "Àllarba", "Alxames", "Àjjuma", "Gaawu"} // weekdayNamesWolofAbbr list the weekday name abbreviations in the Wolof. weekdayNamesWolofAbbr = []string{"Dib.", "Alt.", "Tal.", "Àll.", "Alx.", "Àjj.", "Gaa."} // weekdayNamesXhosa list the weekday name abbreviations in the Xhosa. weekdayNamesXhosa = []string{"Cawe", "Mvulo", "Lwesibini", "Lwesithathu", "Lwesine", "Lwesihlanu", "Mgqibelo"} // weekdayNamesXhosaAbbr list the weekday name abbreviations in the Xhosa. weekdayNamesXhosaAbbr = []string{"iCa.", "uMv.", "uLwesib.", "uLwesith.", "uLwesin.", "uLwesihl.", "uMgq."} // weekdayNamesYi list the weekday name abbreviations in the Yi. weekdayNamesYi = []string{ "\uA46D\uA18F\uA44D", "\uA18F\uA282\uA494", "\uA18F\uA282\uA44D", "\uA18F\uA282\uA315", "\uA18F\uA282\uA1D6", "\uA18F\uA282\uA26C", "\uA18F\uA282\uA0D8", } // weekdayNamesYiAbbr list the weekday name abbreviations in the Yi. weekdayNamesYiAbbr = []string{ "\uA46D\uA18F", "\uA18F\uA494", "\uA18F\uA44D", "\uA18F\uA315", "\uA18F\uA1D6", "\uA18F\uA26C", "\uA18F\uA0D8", } // weekdayNamesYiddish list the weekday name abbreviations in the Yiddish. weekdayNamesYiddish = []string{ "\u05D6\u05D5\u05E0\u05D8\u05D9\u05E7", "\u05DE\u05D0\u05B8\u05E0\u05D8\u05D9\u05E7", "\u05D3\u05D9\u05E0\u05E1\u05D8\u05D9\u05E7", "\u05DE\u05D9\u05D8\u05D5\u05D5\u05D0\u05DA", "\u05D3\u05D0\u05E0\u05E2\u05E8\u05E9\u05D8\u05D9\u05E7", "\u05E4\u05BF\u05E8\u05F2\u05B7\u05D8\u05D9\u05E7", "\u05E9\u05D1\u05EA", } // weekdayNamesYiddishAbbr list the weekday name abbreviations in the // Yiddish. weekdayNamesYiddishAbbr = []string{ "\u05D9\u05D5\u05DD \u05D0", "\u05D9\u05D5\u05DD \u05D1", "\u05D9\u05D5\u05DD \u05D2", "\u05D9\u05D5\u05DD \u05D3", "\u05D9\u05D5\u05DD \u05D4", "\u05D9\u05D5\u05DD \u05D5", "\u05E9\u05D1\u05EA", } // weekdayNamesYoruba list the weekday name abbreviations in the Yoruba. weekdayNamesYoruba = []string{ "\u1ECCj\u1ECD\u0301 Àìkú", "\u1ECCj\u1ECD\u0301 Ajé", "\u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun", "\u1ECCj\u1ECD\u0301rú", "\u1ECCj\u1ECD\u0301b\u1ECD", "\u1ECCj\u1ECD\u0301 \u1EB8tì", "\u1ECCj\u1ECD\u0301 Àbám\u1EB9\u0301ta", } // weekdayNamesYorubaAbbr list the weekday name abbreviations in the Yoruba. weekdayNamesYorubaAbbr = []string{"Àìk", "Aj", "Ì\u1E63g", "\u1ECCjr", "\u1ECCjb", "\u1EB8t", "Àbá"} // weekdayNamesZulu list the weekday name abbreviations in the Zulu. weekdayNamesZulu = []string{"ISonto", "UMsombuluko", "ULwesibili", "ULwesithathu", "ULwesine", "ULwesihlanu", "UMgqibelo"} // weekdayNamesZuluAbbr list the weekday name abbreviations in the Zulu. weekdayNamesZuluAbbr = []string{"Son.", "Mso.", "Bi.", "Tha.", "Ne.", "Hla.", "Mgq."} // apFmtAfrikaans defined the AM/PM name in the Afrikaans. apFmtAfrikaans = "vm./nm." // apFmtAlbanian defined the AM/PM name in the Albanian. apFmtAlbanian = "p.d./m.d." // apFmtAlsatian defined the AM/PM name in the Alsatian. apFmtAlsatian = "vorm./nam." // apFmtAmharic defined the AM/PM name in the Amharic. apFmtAmharic = "\u1325\u12CB\u1275/\u12A8\u1230\u12D3\u1275" // apFmtArabic defined the AM/PM name in the Arabic. apFmtArabic = "\u0635/\u0645" // apFmtAssamese defined the AM/PM name in the Assamese. apFmtAssamese = "\u09F0\u09BE\u09A4\u09BF\u09AA\u09C1/\u0986\u09AC\u09C7\u09B2\u09BF" // apFmtBreton defined the AM/PM name in the Assamese. apFmtBreton = "A.M./G.M." // apFmtBurmese defined the AM/PM name in the Assamese. apFmtBurmese = "\u1014\u1036\u1014\u1000\u103A/\u100A\u1014\u1031" // apFmtCameroon defined the AM/PM name in the Cameroon. apFmtCameroon = "mat./soir" // apFmtCentralKurdish defined the AM/PM name in the Central Kurdish. apFmtCentralKurdish = "\u067E.\u0646/\u062F.\u0646" // apFmtCuba defined the AM/PM name in the Cuba. apFmtCuba = "a.m./p.m." // apFmtCzech defined the AM/PM name in the Czech. apFmtCzech = "dop./odp." // apFmtDari defined the AM/PM name in the Dari. apFmtDari = "\u0642.\u0638./\u0628.\u0638." // apFmtDivehi defined the AM/PM name in the Divehi. apFmtDivehi = "\u0789\u0786/\u0789\u078A" // apFmtDzongkha defined the AM/PM name in the Dzongkha. apFmtDzongkha = "\u0F66\u0F94\u0F0B\u0F46\u0F0B/\u0F55\u0FB1\u0F72\u0F0B\u0F46\u0F0B" // apFmtFaroese defined the AM/PM name in the Faroese. apFmtFaroese = "um fyr./um sein." // apFmtFinnish defined the AM/PM name in the Finnish. apFmtFinnish = "ap./ip." // apFmtGreek defined the AM/PM name in the Greek. apFmtGreek = "\u03C0\u03BC/\u03BC\u03BC" // apFmtGujarati defined the AM/PM name in the Gujarati. apFmtGujarati = "\u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8/\u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8" // apFmtHindi defined the AM/PM name in the Hindi. apFmtHindi = "\u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928/\u0905\u092A\u0930\u093E\u0939\u094D\u0928" // apFmtHungarian defined the AM/PM name in the Hungarian. apFmtHungarian = "de./du." // apFmtIcelandic defined the AM/PM name in the Icelandic. apFmtIcelandic = "f.h./e.h." // apFmtIgbo defined the AM/PM name in the Igbo. apFmtIgbo = "A.M./P.M." // apFmtIrish defined the AM/PM name in the Irish. apFmtIrish = "r.n./i.n." // apFmtJapanese defined the AM/PM name in the Japanese. apFmtJapanese = "午前/午後" // apFmtKannada defined the AM/PM name in the Kannada. apFmtKannada = "\u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8/\u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8" // apFmtKhmer defined the AM/PM name in the Khmer. apFmtKhmer = "\u1796\u17D2\u179A\u17B9\u1780/\u179B\u17D2\u1784\u17B6\u1785" // apFmtKonkani defined the AM/PM name in the Konkani. apFmtKonkani = "\u092E.\u092A\u0942./\u092E.\u0928\u0902." // apFmtKorean defined the AM/PM name in the Korean. apFmtKorean = "오전/오후" // apFmtKyrgyz defined the AM/PM name in the Kyrgyz. apFmtKyrgyz = "\u0442\u04A3/\u0442\u043A" // apFmtLao defined the AM/PM name in the Lao. apFmtLao = "\u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87/\u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87" // apFmtLatvian defined the AM/PM name in the Latvian. apFmtLatvian = "priekšp./pēcp." // apFmtLithuanian defined the AM/PM name in the Lithuanian. apFmtLithuanian = "priešpiet/popiet" // apFmtMacedonian defined the AM/PM name in the Macedonian. apFmtMacedonian = "\u043F\u0440\u0435\u0442\u043F\u043B./\u043F\u043E\u043F\u043B." // apFmtMalay defined the AM/PM name in the Malay. apFmtMalay = "PG/PTG" // apFmtMongolian defined the AM/PM name in the Mongolian. apFmtMongolian = "\u04AF.\u04E9./\u04AF.\u0445." // apFmtNigeria defined the AM/PM name in the Nigeria. apFmtNigeria = "subaka/kikiiɗe" // apFmtNorwegian defined the AM/PM name in the Norwegian. apFmtNorwegian = "f.m./e.m." // apFmtOromo defined the AM/PM name in the Oromo. apFmtOromo = "WD/WB" // apFmtPashto defined the AM/PM name in the Pashto. apFmtPashto = "\u063A.\u0645./\u063A.\u0648." // apFmtPersian defined the AM/PM name in the Persian. apFmtPersian = "\u0642.\u0638/\u0628.\u0638" // apFmtPunjabi defined the AM/PM name in the Punjabi. apFmtPunjabi = "\u0A38\u0A35\u0A47\u0A30/\u0A38\u0A3C\u0A3E\u0A2E" // apFmtSakha defined the AM/PM name in the Sakha. apFmtSakha = "\u041A\u0418/\u041A\u041A" // apFmtSamiNorthern defined the AM/PM name in the Sami (Northern). apFmtSamiNorthern = "i.b./e.b." // apFmtSanskrit defined the AM/PM name in the Sanskrit. apFmtSanskrit = "\u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935/\u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924" // apFmtScottishGaelic defined the AM/PM name in the Scottish Gaelic. apFmtScottishGaelic = "m/f" // apFmtSerbianLatin defined the AM/PM name in the Serbian (Latin). apFmtSerbianLatin = "pre podne/po podne" // apFmtSerbianLatinBA defined the AM/PM name in the Serbian (Latin) Bosnia // and Herzegovina. apFmtSerbianLatinBA = "prije podne/po podne" // apFmtSinhala defined the AM/PM name in the Sinhala. apFmtSinhala = "\u0DB4\u0DD9.\u0DC0./\u0DB4.\u0DC0." // apFmtSlovenian defined the AM/PM name in the Slovenian. apFmtSlovenian = "dop./pop." // apFmtSomali defined the AM/PM name in the Somali. apFmtSomali = "GH/GD" // apFmtSpanish defined the AM/PM name in the Spanish. apFmtSpanish = "a. m./p. m." // apFmtSpanishAR defined the AM/PM name in the Spanish Argentina. apFmtSpanishAR = "a. m./p. m." // apFmtSwedish defined the AM/PM name in the Swedish. apFmtSwedish = "fm/em" // apFmtSyriac defined the AM/PM name in the Syriac. apFmtSyriac = "\u0729.\u071B/\u0712.\u071B" // apFmtTamil defined the AM/PM name in the Tamil. apFmtTamil = "\u0B95\u0BBE\u0BB2\u0BC8/\u0BAE\u0BBE\u0BB2\u0BC8" // apFmtTibetan defined the AM/PM name in the Tibetan. apFmtTibetan = "\u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B/\u0F55\u0FB1\u0F72\u0F0B\u0F51\u0FB2\u0F7C\u0F0B" // apFmtTigrinya defined the AM/PM name in the Tigrinya. apFmtTigrinya = "\u1295\u1309\u1206/\u12F5\u1215\u122A \u1250\u1275\u122A" // apFmtTigrinyaER defined the AM/PM name in the Tigrinya Eritrea. apFmtTigrinyaER = "\u1295\u1309\u1206 \u1230\u12D3\u1270/\u12F5\u1215\u122D \u1230\u12D3\u1275" // apFmtTurkish defined the AM/PM name in the Turkish. apFmtTurkish = "\u00F6\u00F6/\u00F6\u0053" // apFmtUpperSorbian defined the AM/PM name in the Upper Sorbian. apFmtUpperSorbian = "dopołdnja/popołdnju" // apFmtUrdu defined the AM/PM name in the Urdu. apFmtUrdu = "\u062F\u0646/\u0631\u0627\u062A" // apFmtUyghur defined the AM/PM name in the Uyghur. apFmtUyghur = "\u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646/\u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646" // apFmtUzbek defined the AM/PM name in the Uzbek. apFmtUzbek = "TO/TK" // apFmtUzbekCyrillic defined the AM/PM name in the Uzbek Cyrillic. apFmtUzbekCyrillic = "\u0422\u041E/\u0422\u041A" // apFmtVietnamese defined the AM/PM name in the Vietnamese. apFmtVietnamese = "SA/CH" // apFmtWelsh defined the AM/PM name in the Welsh. apFmtWelsh = "yb/yh" // apFmtWolof defined the AM/PM name in the Wolof. apFmtWolof = "Sub/Ngo" // apFmtYi defined the AM/PM name in the Yi. apFmtYi = "\uA3B8\uA111/\uA06F\uA2D2" // apFmtYiddish defined the AM/PM name in the Yiddish. apFmtYiddish = "\u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2/\u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2" // apFmtYoruba defined the AM/PM name in the Yoruba. apFmtYoruba = "Àár\u1ECD\u0300/\u1ECC\u0300sán" // switchArgumentFunc defined the switch argument printer function. switchArgumentFunc = map[string]func(s string) string{ "[DBNum1]": func(s string) string { r := strings.NewReplacer( "0", "\u25CB", "1", "\u4E00", "2", "\u4E8C", "3", "\u4E09", "4", "\u56DB", "5", "\u4E94", "6", "\u516D", "7", "\u4E03", "8", "\u516B", "9", "\u4E5D", ) return r.Replace(s) }, "[DBNum2]": func(s string) string { r := strings.NewReplacer( "0", "\u96F6", "1", "\u58F9", "2", "\u8D30", "3", "\u53C1", "4", "\u8086", "5", "\u4F0D", "6", "\u9646", "7", "\u67D2", "8", "\u634C", "9", "\u7396", ) return r.Replace(s) }, "[DBNum3]": func(s string) string { r := strings.NewReplacer( "0", "\uFF10", "1", "\uFF11", "2", "\uFF12", "3", "\uFF13", "4", "\uFF14", "5", "\uFF15", "6", "\uFF16", "7", "\uFF17", "8", "\uFF18", "9", "\uFF19", ) return r.Replace(s) }, } // langNumFmtFunc defines functions to apply language number format code. langNumFmtFunc = map[CultureName]func(f *File, numFmtID int) string{ CultureNameEnUS: func(f *File, numFmtID int) string { return f.langNumFmtFuncEnUS(numFmtID) }, CultureNameJaJP: func(f *File, numFmtID int) string { return f.langNumFmtFuncJaJP(numFmtID) }, CultureNameKoKR: func(f *File, numFmtID int) string { return f.langNumFmtFuncKoKR(numFmtID) }, CultureNameZhCN: func(f *File, numFmtID int) string { return f.langNumFmtFuncZhCN(numFmtID) }, CultureNameZhTW: func(f *File, numFmtID int) string { return f.langNumFmtFuncZhTW(numFmtID) }, } ) // getSupportedLanguageInfo returns language information by giving language // code. This function does not support different calendar type of the language // currently. For example: the hexadecimal language code 3010429 (fa-IR,301) // will be convert to 0429 (fa-IR). func getSupportedLanguageInfo(lang string) (languageInfo, bool) { hex := lang if len(hex) > 4 { hex = hex[len(hex)-4:] } n := new(big.Int) n.SetString(hex, 16) if info, ok := supportedLanguageInfo[int(n.Int64())]; ok { return info, ok } if info, ok := supportedLanguageCodeInfo[lang]; ok { return info, ok } for _, info := range supportedLanguageInfo { if inStrSlice(info.tags, lang, false) != -1 { return info, true } } return languageInfo{}, false } // applyBuiltInNumFmt provides a function to returns a value after formatted // with built-in number format code, or specified sort date format code. func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string { if f.options != nil && f.options.ShortDatePattern != "" { if numFmtID == 14 { fmtCode = f.options.ShortDatePattern } if numFmtID == 22 { fmtCode = fmt.Sprintf("%s hh:mm", f.options.ShortDatePattern) } } return format(c.V, fmtCode, date1904, cellType, f.options) } // langNumFmtFuncEnUS returns number format code by given date and time pattern // for country code en-us. func (f *File) langNumFmtFuncEnUS(numFmtID int) string { shortDatePattern, longTimePattern := "M/d/yy", "h:mm:ss" if f.options.ShortDatePattern != "" { shortDatePattern = f.options.ShortDatePattern } if f.options.LongTimePattern != "" { longTimePattern = f.options.LongTimePattern } if 32 <= numFmtID && numFmtID <= 35 { return longTimePattern } if (27 <= numFmtID && numFmtID <= 31) || (50 <= numFmtID && numFmtID <= 58) { return shortDatePattern } return "" } // langNumFmtFuncJaJP returns number format code by given date and time pattern // for country code ja-jp. func (f *File) langNumFmtFuncJaJP(numFmtID int) string { if numFmtID == 30 && f.options.ShortDatePattern != "" { return f.options.ShortDatePattern } if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" { return f.options.LongTimePattern } return langNumFmt["ja-jp"][numFmtID] } // langNumFmtFuncKoKR returns number format code by given date and time pattern // for country code ko-kr. func (f *File) langNumFmtFuncKoKR(numFmtID int) string { if numFmtID == 30 && f.options.ShortDatePattern != "" { return f.options.ShortDatePattern } if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" { return f.options.LongTimePattern } return langNumFmt["ko-kr"][numFmtID] } // langNumFmtFuncZhCN returns number format code by given date and time pattern // for country code zh-cn. func (f *File) langNumFmtFuncZhCN(numFmtID int) string { if numFmtID == 30 && f.options.ShortDatePattern != "" { return f.options.ShortDatePattern } if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" { return f.options.LongTimePattern } return langNumFmt["zh-cn"][numFmtID] } // langNumFmtFuncZhTW returns number format code by given date and time pattern // for country code zh-tw. func (f *File) langNumFmtFuncZhTW(numFmtID int) string { if numFmtID == 30 && f.options.ShortDatePattern != "" { return f.options.ShortDatePattern } if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" { return f.options.LongTimePattern } return langNumFmt["zh-tw"][numFmtID] } // checkDateTimePattern check and validate date and time options field value. func (f *File) checkDateTimePattern() error { for _, pattern := range []string{f.options.LongDatePattern, f.options.LongTimePattern, f.options.ShortDatePattern} { p := nfp.NumberFormatParser() for _, section := range p.Parse(pattern) { for _, token := range section.Items { if inStrSlice(supportedTokenTypes, token.TType, false) == -1 || inStrSlice(supportedNumberTokenTypes, token.TType, false) != -1 { return ErrUnsupportedNumberFormat } } } } return nil } // extractNumFmtDecimal returns decimal places, if has a decimal point token and // zero place holder token from a number format code token list. func extractNumFmtDecimal(tokens []nfp.Token) (int, bool, bool) { decimal, point, zero := 0, false, false for _, token := range tokens { if token.TType == nfp.TokenTypeDecimalPoint { point = true } if token.TType == nfp.TokenTypeZeroPlaceHolder { if point { decimal = len(token.TValue) } zero = true } } return decimal, point, zero } // extractNumFmtDecimal returns decimal places from a number format code that // has the same decimal places in positive part negative part or only positive // part, if the given number format code is not suitable for numeric this // function will return -1. func (f *File) extractNumFmtDecimal(fmtCode string) int { var ( p = nfp.NumberFormatParser() pos, neg, posPoint, negPoint, posZero, negZero bool posDecimal, negDecimal int ) for i, section := range p.Parse(fmtCode) { if i == 0 { pos = true posDecimal, posPoint, posZero = extractNumFmtDecimal(section.Items) } if i == 1 { neg = true negDecimal, negPoint, negZero = extractNumFmtDecimal(section.Items) } } if !pos { return -1 } equalPosNegDecimal := posPoint && negPoint && posDecimal == negDecimal equalPosNegZero := !posPoint && !negPoint && posZero && negZero if neg { if equalPosNegDecimal { return posDecimal } if equalPosNegZero { return 0 } return -1 } if posPoint { return posDecimal } if posZero { return 0 } return -1 } // getBuiltInNumFmtCode convert number format index to number format code with // specified locale and language. func (f *File) getBuiltInNumFmtCode(numFmtID int) (string, bool) { if fmtCode, ok := builtInNumFmt[numFmtID]; ok { return fmtCode, true } if isLangNumFmt(numFmtID) { if fn, ok := langNumFmtFunc[f.options.CultureInfo]; ok { return fn(f, numFmtID), true } } return "", false } // prepareNumberic split the number into two before and after parts by a // decimal point. func (nf *numberFormat) prepareNumberic(value string) { if nf.cellType != CellTypeNumber && nf.cellType != CellTypeDate { return } if nf.isNumeric, _, _ = isNumeric(value); !nf.isNumeric { return } } // format provides a function to return a string parse by number format // expression. If the given number format is not supported, this will return // the original cell value. func format(value, numFmt string, date1904 bool, cellType CellType, opts *Options) string { p := nfp.NumberFormatParser() nf := numberFormat{opts: opts, section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType} nf.number, nf.valueSectionType = nf.getValueSectionType(value) nf.prepareNumberic(value) for i, section := range nf.section { nf.sectionIdx = i if section.Type != nf.valueSectionType { continue } if nf.isNumeric { switch section.Type { case nfp.TokenSectionPositive: return nf.alignmentHandler(nf.positiveHandler()) default: return nf.alignmentHandler(nf.negativeHandler()) } } return nf.alignmentHandler(nf.textHandler()) } return value } // getNumberPartLen returns the length of integer and fraction parts for the // numeric. func (nf *numberFormat) getNumberPartLen() (int, int) { var intPart, fracPart, intLen, fracLen int parts := strings.Split(strconv.FormatFloat(math.Abs(nf.number), 'f', -1, 64), ".") intPart = len(parts[0]) if len(parts) == 2 { fracPart = len(parts[1]) } if nf.intHolder > intPart { nf.intHolder = intPart } if intLen = intPart; nf.intPadding+nf.intHolder > intPart { intLen = nf.intPadding + nf.intHolder } if fracLen = fracPart; fracPart > nf.fracHolder+nf.fracPadding { fracLen = nf.fracHolder + nf.fracPadding } if nf.fracPadding > fracPart { fracLen = nf.fracPadding } return intLen, fracLen } // getNumberFmtConf generate the number format padding and placeholder // configurations. func (nf *numberFormat) getNumberFmtConf() { for _, token := range nf.section[nf.sectionIdx].Items { if token.TType == nfp.TokenTypeHashPlaceHolder { if nf.usePointer { nf.fracHolder += len(token.TValue) continue } nf.intHolder += len(token.TValue) } if token.TType == nfp.TokenTypeExponential { nf.useScientificNotation = true } if token.TType == nfp.TokenTypeThousandsSeparator { nf.useCommaSep = true } if token.TType == nfp.TokenTypePercent { nf.percent += len(token.TValue) } if token.TType == nfp.TokenTypeDecimalPoint { nf.usePointer = true } if token.TType == nfp.TokenTypeFraction { nf.useFraction = true } if token.TType == nfp.TokenTypeSwitchArgument { nf.switchArgument = token.TValue } if token.TType == nfp.TokenTypeZeroPlaceHolder { nf.intHolder = 0 if nf.usePointer { if nf.useScientificNotation { nf.expBaseLen += len(token.TValue) continue } nf.fracPadding += len(token.TValue) continue } nf.intPadding += len(token.TValue) } } } // handleDigitsLiteral apply digit placeholder tokens for the number literal. func handleDigitsLiteral(text string, tokenValueLen, intPartLen, hashZeroPartLen int, fill bool) (int, string) { var result string l := tokenValueLen if intPartLen == 0 && len(text) > hashZeroPartLen { l = len(text) + tokenValueLen - hashZeroPartLen } if len(text) < hashZeroPartLen { intPartLen += len(text) - hashZeroPartLen } for i := 0; i < l; i++ { j := i + intPartLen if 0 <= j && j < len([]rune(text)) { result += string([]rune(text)[j]) } else if fill { result += " " } } return l, result } // printNumberLiteral apply literal tokens for the pre-formatted text. func (nf *numberFormat) printNumberLiteral(text string) string { var ( result string frac float64 useFraction bool intPartLen, hashZeroPartLen int ) if nf.usePositive { result += "-" } lastNonFractionPartDigital := math.MaxInt appearedFraction := false numeratorPlaceHolder := 0 for idx := len(nf.section[nf.sectionIdx].Items) - 1; idx >= 0; idx-- { token := nf.section[nf.sectionIdx].Items[idx] if token.TType == nfp.TokenTypeFraction { appearedFraction = true } else if appearedFraction && (token.TType == nfp.TokenTypeHashPlaceHolder || token.TType == nfp.TokenTypeDigitalPlaceHolder) { lastNonFractionPartDigital = idx - 1 // current idx belongs to the fraction part (numerator) break } } for idx, token := range nf.section[nf.sectionIdx].Items { switch token.TType { case nfp.TokenTypeHashPlaceHolder, nfp.TokenTypeZeroPlaceHolder: hashZeroPartLen += len(token.TValue) case nfp.TokenTypeDigitalPlaceHolder: if lastNonFractionPartDigital >= 0 && idx < lastNonFractionPartDigital { hashZeroPartLen += len(token.TValue) } } } for idx, token := range nf.section[nf.sectionIdx].Items { if token.TType == nfp.TokenTypeCurrencyLanguage { _, _ = nf.currencyLanguageHandler(token) result += nf.currencyString } if token.TType == nfp.TokenTypeLiteral { result += token.TValue } if token.TType == nfp.TokenTypeDigitalPlaceHolder && idx > lastNonFractionPartDigital { // If it is a fraction part, it will be filled in by the fractalHandler() // If not, fill placeHolder in directly here if !useFraction { // numerator part numeratorPlaceHolder = len(token.TValue) } } else if token.TType == nfp.TokenTypeHashPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder || token.TType == nfp.TokenTypeDigitalPlaceHolder { digits, str := handleDigitsLiteral(text, len(token.TValue), intPartLen, hashZeroPartLen, token.TType == nfp.TokenTypeDigitalPlaceHolder) intPartLen += digits result += str } if token.TType == nfp.TokenTypeFraction { _, frac = math.Modf(nf.number) frac, useFraction = math.Abs(frac), true } if useFraction { result += nf.fractionHandler(frac, token, numeratorPlaceHolder) } } return nf.printSwitchArgument(result) } // fractionHandler handling fraction number format expression for positive and // negative numeric. func (nf *numberFormat) fractionHandler(frac float64, token nfp.Token, numeratorPlaceHolder int) string { var rat, result string if token.TType == nfp.TokenTypeDigitalPlaceHolder { rat = floatToFraction(frac, numeratorPlaceHolder, len(token.TValue)) result += rat } if token.TType == nfp.TokenTypeDenominator { denom, _ := strconv.ParseFloat(token.TValue, 64) result += fmt.Sprintf("%d/%d", int(math.Round(frac*denom)), int(math.Round(denom))) } return result } // printCommaSep format number with thousands separator. func printCommaSep(text string) string { var ( target strings.Builder subStr = strings.Split(text, ".") length = len(subStr[0]) ) for i := 0; i < length; i++ { if i > 0 && (length-i)%3 == 0 { target.WriteString(",") } target.WriteByte(text[i]) } if len(subStr) == 2 { target.WriteString(".") target.WriteString(subStr[1]) } return target.String() } // printSwitchArgument format number with switch argument. func (nf *numberFormat) printSwitchArgument(text string) string { if nf.switchArgument == "" { return text } if fn, ok := switchArgumentFunc[nf.switchArgument]; ok { return fn(text) } return nf.value } // printBigNumber format number which precision great than 15 with fraction // zero padding and percentage symbol. func (nf *numberFormat) printBigNumber(decimal float64, fracLen int) string { var exp float64 if nf.percent > 0 { exp = 1 } result := strings.TrimLeft(strconv.FormatFloat(decimal*math.Pow(100, exp), 'f', -1, 64), "-") if nf.useCommaSep { result = printCommaSep(result) } if fracLen > 0 { if parts := strings.Split(result, "."); len(parts) == 2 { fracPartLen := len(parts[1]) if fracPartLen < fracLen { result = fmt.Sprintf("%s%s", result, strings.Repeat("0", fracLen-fracPartLen)) } if fracPartLen > fracLen { result = fmt.Sprintf("%s.%s", parts[0], parts[1][:fracLen]) } } else { result = fmt.Sprintf("%s.%s", result, strings.Repeat("0", fracLen)) } } if nf.percent > 0 { return fmt.Sprintf("%s%%", result) } return result } // numberHandler handling number format expression for positive and negative // numeric. func (nf *numberFormat) numberHandler() string { nf.getNumberFmtConf() var ( num = nf.number intLen, fracLen = nf.getNumberPartLen() result string ) if intLen+fracLen > 15 && !nf.useScientificNotation { isNum, precision, decimal := isNumeric(nf.value) if isNum && precision > 15 { return nf.printNumberLiteral(nf.printBigNumber(decimal, fracLen)) } } paddingLen := intLen + fracLen if fracLen > 0 { paddingLen++ } fmtCode := fmt.Sprintf("%%0%d.%df%s", paddingLen, fracLen, strings.Repeat("%%", nf.percent)) if nf.useScientificNotation { if nf.expBaseLen != 2 { return nf.value } fmtCode = fmt.Sprintf("%%.%dE%s", fracLen, strings.Repeat("%%", nf.percent)) } if nf.percent > 0 { num *= math.Pow(100, float64(nf.percent)) } if nf.useFraction { num = math.Floor(math.Abs(num)) } if !nf.useScientificNotation { ratio := math.Pow(10, float64(fracLen)) num = math.Round(num*ratio) / ratio } if result = fmt.Sprintf(fmtCode, math.Abs(num)); nf.useCommaSep { result = printCommaSep(result) } return nf.printNumberLiteral(result) } // dateTimeHandler handling data and time number format expression for a // positive numeric. func (nf *numberFormat) dateTimeHandler() string { nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false if !nf.useMillisecond { nf.t = nf.t.Add(time.Duration(math.Round(float64(nf.t.Nanosecond())/1e9)) * time.Second) } for i, token := range nf.section[nf.sectionIdx].Items { if token.TType == nfp.TokenTypeCurrencyLanguage { if changeNumFmtCode, err := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode { return nf.value } nf.result += nf.currencyString } if token.TType == nfp.TokenTypeDateTimes { nf.dateTimesHandler(i, token) } if token.TType == nfp.TokenTypeElapsedDateTimes { nf.elapsedDateTimesHandler(token) } if token.TType == nfp.TokenTypeLiteral { nf.result += token.TValue continue } if token.TType == nfp.TokenTypeDecimalPoint { nf.result += "." } if token.TType == nfp.TokenTypeSwitchArgument { nf.switchArgument = token.TValue } if token.TType == nfp.TokenTypeZeroPlaceHolder { zeroHolderLen := len(token.TValue) if zeroHolderLen > 3 { zeroHolderLen = 3 } nf.result += fmt.Sprintf("%03d", nf.t.Nanosecond()/1e6)[:zeroHolderLen] } } return nf.printSwitchArgument(nf.result) } // alignmentHandler will be handling alignment token for each number format // selection for a number format expression. func (nf *numberFormat) alignmentHandler(result string) string { tokens := nf.section[nf.sectionIdx].Items if len(tokens) == 0 { return result } if tokens[0].TType == nfp.TokenTypeAlignment { result = nfp.Whitespace + result } if l := len(tokens); tokens[l-1].TType == nfp.TokenTypeAlignment { result += nfp.Whitespace } return result } // positiveHandler will be handling positive selection for a number format // expression. func (nf *numberFormat) positiveHandler() string { var fmtNum bool for _, token := range nf.section[nf.sectionIdx].Items { if token.TType == nfp.TokenTypeGeneral { if isNum, precision, _ := isNumeric(nf.value); isNum && precision > 11 { return strconv.FormatFloat(nf.number, 'G', 10, 64) } return nf.value } if inStrSlice(supportedNumberTokenTypes, token.TType, true) != -1 { fmtNum = true } if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 { if fmtNum || nf.number < 0 { return nf.value } var useDateTimeTokens bool for _, token := range nf.section[nf.sectionIdx].Items { if inStrSlice(supportedDateTimeTokenTypes, token.TType, false) != -1 { if useDateTimeTokens && nf.useMillisecond { return nf.value } useDateTimeTokens = true } if inStrSlice(supportedNumberTokenTypes, token.TType, false) != -1 { if token.TType == nfp.TokenTypeZeroPlaceHolder { nf.useMillisecond = true continue } return nf.value } } return nf.dateTimeHandler() } } return nf.numberHandler() } // currencyLanguageHandler will be handling currency and language types tokens // for a number format expression. func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (bool, error) { for _, part := range token.Parts { if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 { return false, ErrUnsupportedNumberFormat } if part.Token.TType == nfp.TokenSubTypeLanguageInfo { if inStrSlice([]string{"F800", "x-sysdate", "1010000"}, part.Token.TValue, false) != -1 { if nf.opts != nil && nf.opts.LongDatePattern != "" { nf.value = format(nf.value, nf.opts.LongDatePattern, nf.date1904, nf.cellType, nf.opts) return true, nil } part.Token.TValue = "409" } if inStrSlice([]string{"F400", "x-systime"}, part.Token.TValue, false) != -1 { if nf.opts != nil && nf.opts.LongTimePattern != "" { nf.value = format(nf.value, nf.opts.LongTimePattern, nf.date1904, nf.cellType, nf.opts) return true, nil } part.Token.TValue = "409" } if _, ok := getSupportedLanguageInfo(strings.ToUpper(part.Token.TValue)); !ok { return false, ErrUnsupportedNumberFormat } nf.localCode = strings.ToUpper(part.Token.TValue) } if part.Token.TType == nfp.TokenSubTypeCurrencyString { nf.currencyString = part.Token.TValue return false, nil } } return false, nil } // localAmPm return AM/PM name by supported language ID. func (nf *numberFormat) localAmPm(ap string) string { if languageInfo, ok := getSupportedLanguageInfo(nf.localCode); ok { return languageInfo.apFmt } return ap } // localMonthsNameAfrikaans returns the Afrikaans name of the month. func localMonthsNameAfrikaans(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAfrikaansAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAfrikaans[int(t.Month())-1] } return monthNamesAfrikaansAbbr[int(t.Month())-1][:1] } // localMonthsNameAlbanian returns the Albanian name of the month. func localMonthsNameAlbanian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAlbanianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAlbanian[int(t.Month())-1] } return monthNamesAlbanianAbbr[int(t.Month())-1][:1] } // localMonthsNameAlsatian returns the Alsatian name of the month. func localMonthsNameAlsatian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAlsatianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAlsatian[int(t.Month())-1] } return monthNamesAlsatianAbbr[int(t.Month())-1][:1] } // localMonthsNameAlsatianFrance returns the Alsatian France name of the month. func localMonthsNameAlsatianFrance(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAlsatianFranceAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAlsatianFrance[int(t.Month())-1] } return monthNamesAlsatianFranceAbbr[int(t.Month())-1][:1] } // localMonthsNameAmharic returns the Amharic name of the month. func localMonthsNameAmharic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAmharicAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAmharic[int(t.Month())-1] } return string([]rune(monthNamesAmharic[int(t.Month())-1])[:1]) } // localMonthsNameArabic returns the Arabic name of the month. func localMonthsNameArabic(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesArabic[int(t.Month())-1])[:1]) } return monthNamesArabic[int(t.Month())-1] } // localMonthsNameArabicIraq returns the Arabic Iraq name of the month. func localMonthsNameArabicIraq(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesArabicIraq[int(t.Month())-1])[:1]) } return monthNamesArabicIraq[int(t.Month())-1] } // localMonthsNameArmenian returns the Armenian name of the month. func localMonthsNameArmenian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesArmenianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesArmenian[int(t.Month())-1] } return string([]rune(monthNamesArmenianAbbr[int(t.Month())-1])[:1]) } // localMonthsNameAssamese returns the Assamese name of the month. func localMonthsNameAssamese(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAssameseAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAssamese[int(t.Month())-1] } return string([]rune(monthNamesAssameseAbbr[int(t.Month())-1])[:1]) } // localMonthsNameAzerbaijaniCyrillic returns the Azerbaijani (Cyrillic) name of the month. func localMonthsNameAzerbaijaniCyrillic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAzerbaijaniCyrillicAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAzerbaijaniCyrillic[int(t.Month())-1] } return string([]rune(monthNamesAzerbaijaniCyrillic[int(t.Month())-1])[:1]) } // localMonthsNameAzerbaijani returns the Azerbaijani name of the month. func localMonthsNameAzerbaijani(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAzerbaijaniAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAzerbaijani[int(t.Month())-1] } return string([]rune(monthNamesAzerbaijani[int(t.Month())-1])[:1]) } // localMonthsNameAustria returns the Austria name of the month. func localMonthsNameAustria(t time.Time, abbr int) string { if abbr == 3 { return monthNamesAustriaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesAustria[int(t.Month())-1] } return monthNamesAustriaAbbr[int(t.Month())-1][:1] } // localMonthsNameBangla returns the German name of the month. func localMonthsNameBangla(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesBangla[int(t.Month())-1] } return string([]rune(monthNamesBangla[int(t.Month())-1])[:1]) } // localMonthsNameBashkir returns the Bashkir name of the month. func localMonthsNameBashkir(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBashkirAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBashkir[int(t.Month())-1] } return string([]rune(monthNamesBashkir[int(t.Month())-1])[:1]) } // localMonthsNameBasque returns the Basque name of the month. func localMonthsNameBasque(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBasqueAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBasque[int(t.Month())-1] } return string([]rune(monthNamesBasque[int(t.Month())-1])[:1]) } // localMonthsNameBelarusian returns the Belarusian name of the month. func localMonthsNameBelarusian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBelarusianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBelarusian[int(t.Month())-1] } return string([]rune(monthNamesBelarusian[int(t.Month())-1])[:1]) } // localMonthsNameBosnianCyrillic returns the Bosnian (Cyrillic) name of the month. func localMonthsNameBosnianCyrillic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBosnianCyrillicAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBosnianCyrillic[int(t.Month())-1] } return string([]rune(monthNamesBosnianCyrillic[int(t.Month())-1])[:1]) } // localMonthsNameBosnian returns the Bosnian name of the month. func localMonthsNameBosnian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBosnianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBosnian[int(t.Month())-1] } return string([]rune(monthNamesBosnian[int(t.Month())-1])[:1]) } // localMonthsNameBreton returns the Breton name of the month. func localMonthsNameBreton(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBretonAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBreton[int(t.Month())-1] } return string([]rune(monthNamesBreton[int(t.Month())-1])[:1]) } // localMonthsNameBulgarian returns the Bulgarian name of the month. func localMonthsNameBulgarian(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesBulgarian[int(t.Month())-1])[:3]) } if abbr == 4 { return monthNamesBulgarian[int(t.Month())-1] } return string([]rune(monthNamesBulgarian[int(t.Month())-1])[:1]) } // localMonthsNameBurmese returns the Burmese name of the month. func localMonthsNameBurmese(t time.Time, abbr int) string { if abbr == 3 { return monthNamesBurmeseAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesBurmese[int(t.Month())-1] } return string([]rune(monthNamesBurmese[int(t.Month())-1])[:1]) } // localMonthsNameCaribbean returns the Caribbean name of the month. func localMonthsNameCaribbean(t time.Time, abbr int) string { if abbr == 3 { return monthNamesCaribbeanAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesCaribbean[int(t.Month())-1] } return monthNamesCaribbeanAbbr[int(t.Month())-1][:1] } // localMonthsNameCentralKurdish returns the Central Kurdish name of the month. func localMonthsNameCentralKurdish(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesCentralKurdish[int(t.Month())-1])[:1]) } return monthNamesCentralKurdish[int(t.Month())-1] } // localMonthsNameCherokee returns the Cherokee name of the month. func localMonthsNameCherokee(t time.Time, abbr int) string { if abbr == 3 { return monthNamesCherokeeAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesCherokee[int(t.Month())-1] } return string([]rune(monthNamesCherokee[int(t.Month())-1])[:1]) } // localMonthsNameChinese1 returns the Chinese name of the month. func localMonthsNameChinese1(t time.Time, abbr int) string { if abbr == 3 { return monthNamesChineseNum[t.Month()-1] } if abbr == 4 { return monthNamesChinese[int(t.Month())-1] } return monthNamesChineseAbbr[int(t.Month())-1] } // localMonthsNameChinese2 returns the Chinese name of the month. func localMonthsNameChinese2(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesChinese[int(t.Month())-1] } return monthNamesChineseAbbr[int(t.Month())-1] } // localMonthsNameChinese3 returns the Chinese name of the month. func localMonthsNameChinese3(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesChineseNum[t.Month()-1] } return strconv.Itoa(int(t.Month())) } // localMonthsNameCorsican returns the Corsican name of the month. func localMonthsNameCorsican(t time.Time, abbr int) string { if abbr == 3 { return monthNamesCorsicanAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesCorsican[int(t.Month())-1] } return string([]rune(monthNamesCorsican[int(t.Month())-1])[:1]) } // localMonthsNameCroatian returns the Croatian name of the month. func localMonthsNameCroatian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesCroatianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesCroatian[int(t.Month())-1] } return string([]rune(monthNamesCroatian[int(t.Month())-1])[:1]) } // localMonthsNameCroatianLatin returns the Croatian (Latin) name of the month. func localMonthsNameCroatianLatin(t time.Time, abbr int) string { if abbr == 3 { if t.Month() == 2 { return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:4]) } return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesCroatian[int(t.Month())-1] } return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:1]) } // localMonthsNameCzech returns the Czech name of the month. func localMonthsNameCzech(t time.Time, abbr int) string { if abbr == 3 { return monthNamesCzechAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesCzech[int(t.Month())-1] } return string([]rune(monthNamesCzech[int(t.Month())-1])[:1]) } // localMonthsNameDanish returns the Danish name of the month. func localMonthsNameDanish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesDanishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesDanish[int(t.Month())-1] } return string([]rune(monthNamesDanish[int(t.Month())-1])[:1]) } // localMonthsNameDari returns the Dari name of the month. func localMonthsNameDari(t time.Time, abbr int) string { if abbr == 3 { return monthNamesDariAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesPersian[int(t.Month())-1] } return string([]rune(monthNamesPersian[int(t.Month())-1])[:1]) } // localMonthsNameDariAfghanistan returns the Dari (Afghanistan) name of the // month. func localMonthsNameDariAfghanistan(t time.Time, abbr int) string { if abbr == 3 { return monthNamesDariAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesDari[int(t.Month())-1] } return string([]rune(monthNamesDari[int(t.Month())-1])[:1]) } // localMonthsNameDivehi returns the Divehi name of the month. func localMonthsNameDivehi(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesDivehi[int(t.Month())-1] } return string([]rune(monthNamesDivehi[int(t.Month())-1])[:1]) } // localMonthsNameDutch returns the Dutch name of the month. func localMonthsNameDutch(t time.Time, abbr int) string { if abbr == 3 { return monthNamesDutchAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesDutch[int(t.Month())-1] } return string([]rune(monthNamesDutch[int(t.Month())-1])[:1]) } // localMonthsNameDzongkha returns the Dzongkha name of the month. func localMonthsNameDzongkha(t time.Time, abbr int) string { if abbr == 3 { return monthNamesDzongkhaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesDzongkha[int(t.Month())-1] } return string([]rune(monthNamesDzongkha[int(t.Month())-1])[:1]) } // localMonthsNameEnglish returns the English name of the month. func localMonthsNameEnglish(t time.Time, abbr int) string { if abbr == 3 { return t.Month().String()[:3] } if abbr == 4 { return t.Month().String() } return t.Month().String()[:1] } // localMonthsNameEstonian returns the Estonian name of the month. func localMonthsNameEstonian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesEstonianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesEstonian[int(t.Month())-1] } return monthNamesEstonianAbbr[int(t.Month())-1][:1] } // localMonthsNameFaroese returns the Faroese name of the month. func localMonthsNameFaroese(t time.Time, abbr int) string { if abbr == 3 { return monthsNameFaroeseAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesFaroese[int(t.Month())-1] } return monthsNameFaroeseAbbr[int(t.Month())-1][:1] } // localMonthsNameFilipino returns the Filipino name of the month. func localMonthsNameFilipino(t time.Time, abbr int) string { if abbr == 3 { return monthNamesFilipinoAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesFilipino[int(t.Month())-1] } return fmt.Sprintf("%02d", int(t.Month())) } // localMonthsNameFinnish returns the Finnish name of the month. func localMonthsNameFinnish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesFinnishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesFinnish[int(t.Month())-1] } return fmt.Sprintf("%02d", int(t.Month())) } // localMonthsNameFrench returns the French name of the month. func localMonthsNameFrench(t time.Time, abbr int) string { if abbr == 3 { month := monthNamesFrench[int(t.Month())-1] if len([]rune(month)) <= 4 { return monthNamesFrench[int(t.Month())-1] } return monthNamesFrenchAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesFrench[int(t.Month())-1] } return monthNamesFrenchAbbr[int(t.Month())-1][:1] } // localMonthsNameFrisian returns the Frisian name of the month. func localMonthsNameFrisian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesFrisianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesFrisian[int(t.Month())-1] } return monthNamesFrisian[int(t.Month())-1][:1] } // localMonthsNameFulah returns the Fulah name of the month. func localMonthsNameFulah(t time.Time, abbr int) string { if abbr == 3 { return monthNamesFulahAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesFulah[int(t.Month())-1] } return monthNamesFulah[int(t.Month())-1][:1] } // localMonthsNameGalician returns the Galician name of the month. func localMonthsNameGalician(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGalicianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesGalician[int(t.Month())-1] } return monthNamesGalician[int(t.Month())-1][:1] } // localMonthsNameGeorgian returns the Georgian name of the month. func localMonthsNameGeorgian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGeorgianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesGeorgian[int(t.Month())-1] } return string([]rune(monthNamesGeorgian[int(t.Month())-1])[:1]) } // localMonthsNameGerman returns the German name of the month. func localMonthsNameGerman(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGermanAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesGerman[int(t.Month())-1] } return monthNamesGermanAbbr[int(t.Month())-1][:1] } // localMonthsNameGreek returns the Greek name of the month. func localMonthsNameGreek(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGreekAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesGreek[int(t.Month())-1] } return string([]rune(monthNamesGreekAbbr[int(t.Month())-1])[:1]) } // localMonthsNameGreenlandic returns the Greenlandic name of the month. func localMonthsNameGreenlandic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGreenlandicAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesGreenlandic[int(t.Month())-1] } return string([]rune(monthNamesGreenlandicAbbr[int(t.Month())-1])[:1]) } // localMonthsNameGuarani returns the Guarani name of the month. func localMonthsNameGuarani(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGuaraniAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesGuarani[int(t.Month())-1] } return string([]rune(monthNamesGuaraniAbbr[int(t.Month())-1])[:1]) } // localMonthsNameGujarati returns the Gujarati name of the month. func localMonthsNameGujarati(t time.Time, abbr int) string { if abbr == 3 { return monthNamesGujaratiAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesGujarati[int(t.Month())-1] } return string([]rune(monthNamesGujaratiAbbr[int(t.Month())-1])[:1]) } // localMonthsNameHausa returns the Hausa name of the month. func localMonthsNameHausa(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesHausa[int(t.Month())-1])[:3]) } if abbr == 4 { return monthNamesHausa[int(t.Month())-1] } return string([]rune(monthNamesHausa[int(t.Month())-1])[:1]) } // localMonthsNameHawaiian returns the Hawaiian name of the month. func localMonthsNameHawaiian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesHawaiianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesHawaiian[int(t.Month())-1] } return string([]rune(monthNamesHawaiianAbbr[int(t.Month())-1])[:1]) } // localMonthsNameHebrew returns the Hebrew name of the month. func localMonthsNameHebrew(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesHebrew[int(t.Month())-1])[:3]) } if abbr == 4 { return monthNamesHebrew[int(t.Month())-1] } return string([]rune(monthNamesHebrew[int(t.Month())-1])[:1]) } // localMonthsNameHindi returns the Hindi name of the month. func localMonthsNameHindi(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesHindi[int(t.Month())-1] } return string([]rune(monthNamesHindi[int(t.Month())-1])[:1]) } // localMonthsNameHungarian returns the Hungarian name of the month. func localMonthsNameHungarian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesHungarianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesHungarian[int(t.Month())-1] } return string([]rune(monthNamesHungarianAbbr[int(t.Month())-1])[:1]) } // localMonthsNameIcelandic returns the Icelandic name of the month. func localMonthsNameIcelandic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesIcelandicAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesIcelandic[int(t.Month())-1] } return string([]rune(monthNamesIcelandicAbbr[int(t.Month())-1])[:1]) } // localMonthsNameIgbo returns the Igbo name of the month. func localMonthsNameIgbo(t time.Time, abbr int) string { if abbr == 3 { return monthNamesIgboAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesIgbo[int(t.Month())-1] } return string([]rune(monthNamesIgboAbbr[int(t.Month())-1])[:1]) } // localMonthsNameIndonesian returns the Indonesian name of the month. func localMonthsNameIndonesian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesIndonesianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesIndonesian[int(t.Month())-1] } return string([]rune(monthNamesIndonesianAbbr[int(t.Month())-1])[:1]) } // localMonthsNameInuktitut returns the Inuktitut name of the month. func localMonthsNameInuktitut(t time.Time, abbr int) string { if abbr == 3 { return monthNamesInuktitutAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesInuktitut[int(t.Month())-1] } return string([]rune(monthNamesInuktitutAbbr[int(t.Month())-1])[:1]) } // localMonthsNameIrish returns the Irish name of the month. func localMonthsNameIrish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesIrishAbbr[(t.Month() - 1)] } if abbr == 4 { return monthNamesIrish[int(t.Month())-1] } return monthNamesIrishAbbr[int(t.Month())-1][:1] } // localMonthsNameItalian returns the Italian name of the month. func localMonthsNameItalian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesItalianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesItalian[int(t.Month())-1] } return monthNamesItalianAbbr[int(t.Month())-1][:1] } // localMonthsNameKannada returns the Kannada name of the month. func localMonthsNameKannada(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKannadaAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKannada[int(t.Month())-1] } return string([]rune(monthNamesKannada[int(t.Month())-1])[:1]) } // localMonthsNameKashmiri returns the Kashmiri name of the month. func localMonthsNameKashmiri(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesKashmiri[int(t.Month())-1])[:1]) } return monthNamesKashmiri[int(t.Month())-1] } // localMonthsNameKazakh returns the Kazakh name of the month. func localMonthsNameKazakh(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKazakhAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKazakh[int(t.Month())-1] } return string([]rune(monthNamesKazakh[int(t.Month())-1])[:1]) } // localMonthsNameKhmer returns the Khmer name of the month. func localMonthsNameKhmer(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKhmerAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKhmer[int(t.Month())-1] } return string([]rune(monthNamesKhmerAbbr[int(t.Month())+11])[:1]) } // localMonthsNameKiche returns the Kiche name of the month. func localMonthsNameKiche(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKicheAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKiche[int(t.Month())-1] } return string([]rune(monthNamesKicheAbbr[(t.Month() - 1)])[:1]) } // localMonthsNameKinyarwanda returns the Kinyarwanda name of the month. func localMonthsNameKinyarwanda(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKinyarwandaAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKinyarwanda[int(t.Month())-1] } return string([]rune(monthNamesKinyarwanda[(t.Month() - 1)])[:1]) } // localMonthsNameKiswahili returns the Kiswahili name of the month. func localMonthsNameKiswahili(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKiswahiliAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKiswahili[int(t.Month())-1] } return string([]rune(monthNamesKiswahili[(t.Month() - 1)])[:1]) } // localMonthsNameKonkani returns the Konkani name of the month. func localMonthsNameKonkani(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKonkaniAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKonkani[int(t.Month())-1] } return string([]rune(monthNamesKonkani[(t.Month() - 1)])[:1]) } // localMonthsNameKorean returns the Korean name of the month. func localMonthsNameKorean(t time.Time, abbr int) string { if abbr == 4 || abbr > 6 { return monthNamesKoreanAbbr[int(t.Month())-1] } return strconv.Itoa(int(t.Month())) } // localMonthsNameKyrgyz returns the Kyrgyz name of the month. func localMonthsNameKyrgyz(t time.Time, abbr int) string { if abbr == 3 { return monthNamesKyrgyzAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesKyrgyz[int(t.Month())-1] } return string([]rune(monthNamesKyrgyz[(t.Month() - 1)])[:1]) } // localMonthsNameLao returns the Lao name of the month. func localMonthsNameLao(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLaoAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLao[int(t.Month())-1] } return string([]rune(monthNamesLao[(t.Month() - 1)])[:1]) } // localMonthsNameLatin returns the Latin name of the month. func localMonthsNameLatin(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLatinAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLatin[int(t.Month())-1] } return string([]rune(monthNamesLatin[(t.Month() - 1)])[:1]) } // localMonthsNameLatvian returns the Latvian name of the month. func localMonthsNameLatvian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLatvianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLatvian[int(t.Month())-1] } return string([]rune(monthNamesLatvian[(t.Month() - 1)])[:1]) } // localMonthsNameLithuanian returns the Lithuanian name of the month. func localMonthsNameLithuanian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLithuanianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLithuanian[int(t.Month())-1] } return string([]rune(monthNamesLithuanian[(t.Month() - 1)])[:1]) } // localMonthsNameLowerSorbian returns the LowerSorbian name of the month. func localMonthsNameLowerSorbian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLowerSorbianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLowerSorbian[int(t.Month())-1] } return string([]rune(monthNamesLowerSorbian[(t.Month() - 1)])[:1]) } // localMonthsNameLuxembourgish returns the Luxembourgish name of the month. func localMonthsNameLuxembourgish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesLuxembourgishAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesLuxembourgish[int(t.Month())-1] } return string([]rune(monthNamesLuxembourgish[(t.Month() - 1)])[:1]) } // localMonthsNameMacedonian returns the Macedonian name of the month. func localMonthsNameMacedonian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMacedonianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMacedonian[int(t.Month())-1] } return string([]rune(monthNamesMacedonian[(t.Month() - 1)])[:1]) } // localMonthsNameMalay returns the Malay name of the month. func localMonthsNameMalay(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMalayAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMalay[int(t.Month())-1] } return string([]rune(monthNamesMalay[(t.Month() - 1)])[:1]) } // localMonthsNameMalayalam returns the Malayalam name of the month. func localMonthsNameMalayalam(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMalayalamAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMalayalam[int(t.Month())-1] } return string([]rune(monthNamesMalayalam[(t.Month() - 1)])[:1]) } // localMonthsNameMaltese returns the Maltese name of the month. func localMonthsNameMaltese(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMalteseAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMaltese[int(t.Month())-1] } return string([]rune(monthNamesMaltese[(t.Month() - 1)])[:1]) } // localMonthsNameMaori returns the Maori name of the month. func localMonthsNameMaori(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMaoriAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMaori[int(t.Month())-1] } return string([]rune(monthNamesMaori[(t.Month() - 1)])[:1]) } // localMonthsNameMapudungun returns the Mapudungun name of the month. func localMonthsNameMapudungun(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesMapudungun[(t.Month() - 1)])[:1]) } return monthNamesMapudungun[int(t.Month())-1] } // localMonthsNameMarathi returns the Marathi name of the month. func localMonthsNameMarathi(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMarathiAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMarathi[int(t.Month())-1] } return string([]rune(monthNamesMarathi[(t.Month() - 1)])[:1]) } // localMonthsNameMohawk returns the Mohawk name of the month. func localMonthsNameMohawk(t time.Time, abbr int) string { if abbr == 3 { return t.Month().String()[:3] } if abbr == 4 || abbr > 6 { return monthNamesMohawk[int(t.Month())-1] } return string([]rune(monthNamesMohawk[(t.Month() - 1)])[:1]) } // localMonthsNameMongolian returns the Mongolian name of the month. func localMonthsNameMongolian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMongolianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesMongolian[int(t.Month())-1] } return string([]rune(monthNamesMongolian[(t.Month() - 1)])[:1]) } // localMonthsNameMorocco returns the Morocco name of the month. func localMonthsNameMorocco(t time.Time, abbr int) string { if abbr == 3 { return monthNamesMoroccoAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesFrench[int(t.Month())-1] } return monthNamesFrench[int(t.Month())-1][:1] } // localMonthsNameNepali returns the Nepali name of the month. func localMonthsNameNepali(t time.Time, abbr int) string { if abbr == 3 { return monthNamesNepaliAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesNepali[int(t.Month())-1] } return string([]rune(monthNamesNepali[(t.Month() - 1)])[:1]) } // localMonthsNameNepaliIN returns the India Nepali name of the month. func localMonthsNameNepaliIN(t time.Time, abbr int) string { if abbr == 3 { return monthNamesNepaliINAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesNepaliIN[int(t.Month())-1] } return string([]rune(monthNamesNepaliIN[(t.Month() - 1)])[:1]) } // localMonthsNameNigeria returns the Nigeria name of the month. func localMonthsNameNigeria(t time.Time, abbr int) string { if abbr == 3 { return monthNamesNigeriaAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesNigeria[int(t.Month())-1] } return monthNamesNigeria[int(t.Month())-1][:1] } // localMonthsNameNorwegian returns the Norwegian name of the month. func localMonthsNameNorwegian(t time.Time, abbr int) string { if abbr == 3 { return monthsNameFaroeseAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesNorwegian[int(t.Month())-1] } return string([]rune(monthNamesNorwegian[(t.Month() - 1)])[:1]) } // localMonthsNameOccitan returns the Occitan name of the month. func localMonthsNameOccitan(t time.Time, abbr int) string { if abbr == 3 { return monthNamesOccitanAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesOccitan[int(t.Month())-1] } return string([]rune(monthNamesOccitan[(t.Month() - 1)])[:1]) } // localMonthsNameOdia returns the Odia name of the month. func localMonthsNameOdia(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesOdia[(t.Month() - 1)])[:1]) } return monthNamesOdia[int(t.Month())-1] } // localMonthsNameOromo returns the Oromo name of the month. func localMonthsNameOromo(t time.Time, abbr int) string { if abbr == 3 { return monthNamesOromoAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesOromo[int(t.Month())-1] } return string([]rune(monthNamesOromo[(t.Month() - 1)])[:1]) } // localMonthsNamePashto returns the Pashto name of the month. func localMonthsNamePashto(t time.Time, abbr int) string { if int(t.Month()) == 6 { if abbr == 3 { return "\u0686\u0646\u06AB\u0627 \u069A" } if abbr == 4 || abbr > 6 { return "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649" } } return monthNamesPashto[int(t.Month())-1] } // localMonthsNamePersian returns the Persian name of the month. func localMonthsNamePersian(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesPersian[(t.Month() - 1)])[:1]) } return monthNamesPersian[int(t.Month())-1] } // localMonthsNamePolish returns the Polish name of the month. func localMonthsNamePolish(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesPolish[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesPolish[int(t.Month())-1] } return string([]rune(monthNamesPolish[(t.Month() - 1)])[:1]) } // localMonthsNamePortuguese returns the Portuguese name of the month. func localMonthsNamePortuguese(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesPortuguese[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesPortuguese[int(t.Month())-1] } return string([]rune(monthNamesPortuguese[(t.Month() - 1)])[:1]) } // localMonthsNamePunjabi returns the Punjabi name of the month. func localMonthsNamePunjabi(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesPunjabi[(t.Month() - 1)])[:1]) } return monthNamesPunjabi[int(t.Month())-1] } // localMonthsNamePunjabiArab returns the Punjabi Arab name of the month. func localMonthsNamePunjabiArab(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesPunjabiArab[(t.Month() - 1)])[:1]) } return monthNamesPunjabiArab[int(t.Month())-1] } // localMonthsNameQuechua returns the Quechua name of the month. func localMonthsNameQuechua(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesQuechua[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesQuechua[int(t.Month())-1] } return string([]rune(monthNamesQuechua[(t.Month() - 1)])[:1]) } // localMonthsNameQuechuaEcuador returns the QuechuaEcuador name of the month. func localMonthsNameQuechuaEcuador(t time.Time, abbr int) string { if abbr == 3 { if int(t.Month()) == 1 { return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:4]) } return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesQuechuaEcuador[int(t.Month())-1] } return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:1]) } // localMonthsNameRomanian returns the Romanian name of the month. func localMonthsNameRomanian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesRomanianAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesRomanian[int(t.Month())-1] } return string([]rune(monthNamesRomanian[(t.Month() - 1)])[:1]) } // localMonthsNameRomansh returns the Romansh name of the month. func localMonthsNameRomansh(t time.Time, abbr int) string { if abbr == 3 { return monthNamesRomanshAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesRomansh[int(t.Month())-1] } return string([]rune(monthNamesRomansh[(t.Month() - 1)])[:1]) } // localMonthsNameRussian returns the Russian name of the month. func localMonthsNameRussian(t time.Time, abbr int) string { if abbr == 3 { month := monthNamesRussian[int(t.Month())-1] if len([]rune(month)) <= 4 { return month } return monthNamesRussianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesRussian[int(t.Month())-1] } return string([]rune(monthNamesRussian[int(t.Month())-1])[:1]) } // localMonthsNameSakha returns the Sakha name of the month. func localMonthsNameSakha(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSakhaAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesSakha[int(t.Month())-1] } return string([]rune(monthNamesSakha[(t.Month() - 1)])[:1]) } // localMonthsNameSami returns the Sami name of the month. func localMonthsNameSami(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSamiAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesSami[int(t.Month())-1] } return string([]rune(monthNamesSami[(t.Month() - 1)])[:1]) } // localMonthsNameSamiLule returns the Sami (Lule) name of the month. func localMonthsNameSamiLule(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSamiLuleAbbr[int(t.Month())-1] } if abbr == 4 || abbr > 6 { return monthNamesSamiLule[int(t.Month())-1] } return string([]rune(monthNamesSamiLule[(t.Month() - 1)])[:1]) } // localMonthsNameSamiNorthern returns the Sami (Northern) name of the month. func localMonthsNameSamiNorthern(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSamiNorthernAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSamiNorthern[int(t.Month())-1] } return string([]rune(monthNamesSamiNorthern[(t.Month() - 1)])[:1]) } // localMonthsNameSamiNorthernFI returns the Sami (Northern) Finland name of the // month. func localMonthsNameSamiNorthernFI(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSamiNorthernAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { if int(t.Month()) == 1 { return "ođđajagemánu" } return monthNamesSamiNorthern[int(t.Month())-1] } return string([]rune(monthNamesSamiNorthern[(t.Month() - 1)])[:1]) } // localMonthsNameSamiSkolt returns the Sami (Skolt) name of the month. func localMonthsNameSamiSkolt(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesSamiSkolt[(t.Month() - 1)])[:1]) } return monthNamesSamiSkolt[int(t.Month())-1] } // localMonthsNameSamiSouthern returns the Sami (Southern) name of the month. func localMonthsNameSamiSouthern(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSamiSouthernAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSamiSouthern[int(t.Month())-1] } return string([]rune(monthNamesSamiSouthern[(t.Month() - 1)])[:1]) } // localMonthsNameSanskrit returns the Sanskrit name of the month. func localMonthsNameSanskrit(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesSanskrit[(t.Month() - 1)])[:1]) } return monthNamesSanskrit[int(t.Month())-1] } // localMonthsNameScottishGaelic returns the Scottish Gaelic name of the month. func localMonthsNameScottishGaelic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesScottishGaelicAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesScottishGaelic[int(t.Month())-1] } return string([]rune(monthNamesScottishGaelic[(t.Month() - 1)])[:1]) } // localMonthsNameSerbian returns the Serbian (Cyrillic) name of the month. func localMonthsNameSerbian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSerbianAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSerbian[int(t.Month())-1] } return string([]rune(monthNamesSerbian[(t.Month() - 1)])[:1]) } // localMonthsNameSerbianBA returns the Serbian (Cyrillic) Bosnia and // Herzegovina name of the month. func localMonthsNameSerbianBA(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSerbianBAAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSerbianBA[int(t.Month())-1] } return string([]rune(monthNamesSerbianBA[(t.Month() - 1)])[:1]) } // localMonthsNameSerbianLatin returns the Serbian (Latin) name of the month. func localMonthsNameSerbianLatin(t time.Time, abbr int) string { if abbr == 3 { return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:3]) } if abbr == 4 || abbr > 6 { return monthNamesSerbianLatin[int(t.Month())-1] } return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:1]) } // localMonthsNameSerbianLatinCS returns the Serbian (Latin) name of the month. func localMonthsNameSerbianLatinCS(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSerbianLatinAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSerbianLatin[int(t.Month())-1] } return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:1]) } // localMonthsNameSesothoSaLeboa returns the Sesotho sa Leboa name of the month. func localMonthsNameSesothoSaLeboa(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSesothoSaLeboaAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSesothoSaLeboa[int(t.Month())-1] } return string([]rune(monthNamesSesothoSaLeboa[(t.Month() - 1)])[:1]) } // localMonthsNameSetswana returns the Setswana name of the month. func localMonthsNameSetswana(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSetswanaAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSetswana[int(t.Month())-1] } return string([]rune(monthNamesSetswana[(t.Month() - 1)])[:1]) } // localMonthsNameSindhi returns the Sindhi name of the month. func localMonthsNameSindhi(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesSindhi[(t.Month() - 1)])[:1]) } return monthNamesSindhi[int(t.Month())-1] } // localMonthsNameSinhala returns the Sinhala name of the month. func localMonthsNameSinhala(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSinhalaAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSinhala[int(t.Month())-1] } return string([]rune(monthNamesSinhala[(t.Month() - 1)])[:1]) } // localMonthsNameSlovak returns the Slovak name of the month. func localMonthsNameSlovak(t time.Time, abbr int) string { if abbr == 3 { return strconv.Itoa(int(t.Month())) } if abbr == 4 || abbr > 6 { return monthNamesSlovak[int(t.Month())-1] } return string([]rune(monthNamesSlovak[(t.Month() - 1)])[:1]) } // localMonthsNameSlovenian returns the Slovenian name of the month. func localMonthsNameSlovenian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSlovenianAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSlovenian[int(t.Month())-1] } return string([]rune(monthNamesSlovenian[(t.Month() - 1)])[:1]) } // localMonthsNameSomali returns the Somali name of the month. func localMonthsNameSomali(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSomaliAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSomali[int(t.Month())-1] } return string([]rune(monthNamesSomali[(t.Month() - 1)])[:1]) } // localMonthsNameSotho returns the Sotho name of the month. func localMonthsNameSotho(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSothoAbbr[(t.Month() - 1)] } if abbr == 4 || abbr > 6 { return monthNamesSotho[int(t.Month())-1] } return string([]rune(monthNamesSotho[(t.Month() - 1)])[:1]) } // localMonthsNameSpanish returns the Spanish name of the month. func localMonthsNameSpanish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSpanishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSpanish[int(t.Month())-1] } return monthNamesSpanishAbbr[int(t.Month())-1][:1] } // localMonthsNameSpanishPE returns the Spanish Peru name of the month. func localMonthsNameSpanishPE(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSpanishPEAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSpanishPE[int(t.Month())-1] } return monthNamesSpanishPEAbbr[int(t.Month())-1][:1] } // localMonthsNameSwedish returns the Swedish name of the month. func localMonthsNameSwedish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSwedishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSwedish[int(t.Month())-1] } return monthNamesSwedishAbbr[int(t.Month())-1][:1] } // localMonthsNameSwedishFI returns the Swedish Finland name of the month. func localMonthsNameSwedishFI(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSwedishFIAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSwedish[int(t.Month())-1] } return monthNamesSwedishFIAbbr[int(t.Month())-1][:1] } // localMonthsNameSyriac returns the Syriac name of the month. func localMonthsNameSyriac(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSyriacAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSyriac[int(t.Month())-1] } return string([]rune(monthNamesSyriac[(t.Month() - 1)])[:1]) } // localMonthsNameTajik returns the Tajik name of the month. func localMonthsNameTajik(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTajikAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTajik[int(t.Month())-1] } return string([]rune(monthNamesTajik[(t.Month() - 1)])[:1]) } // localMonthsNameTamazight returns the Tamazight name of the month. func localMonthsNameTamazight(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTamazightAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTamazight[int(t.Month())-1] } return string([]rune(monthNamesTamazight[(t.Month() - 1)])[:1]) } // localMonthsNameTamil returns the Tamil name of the month. func localMonthsNameTamil(t time.Time, abbr int) string { if abbr == 5 { return string([]rune(monthNamesTamil[(t.Month() - 1)])[:1]) } return monthNamesTamil[int(t.Month())-1] } // localMonthsNameTamilLK returns the Tamil Sri Lanka name of the month. func localMonthsNameTamilLK(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTamilAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTamil[int(t.Month())-1] } return string([]rune(monthNamesTamil[(t.Month() - 1)])[:1]) } // localMonthsNameTatar returns the Tatar name of the month. func localMonthsNameTatar(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTatarAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTatar[int(t.Month())-1] } return string([]rune(monthNamesTatar[(t.Month() - 1)])[:1]) } // localMonthsNameTelugu returns the Telugu name of the month. func localMonthsNameTelugu(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTeluguAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTelugu[int(t.Month())-1] } return string([]rune(monthNamesTelugu[(t.Month() - 1)])[:1]) } // localMonthsNameSyllabics returns the Syllabics name of the month. func localMonthsNameSyllabics(t time.Time, abbr int) string { if abbr == 3 { return monthNamesSyllabicsAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesSyllabics[int(t.Month())-1] } return string([]rune(monthNamesSyllabics[int(t.Month())-1])[:1]) } // localMonthsNameThai returns the Thai name of the month. func localMonthsNameThai(t time.Time, abbr int) string { if abbr == 3 { r := []rune(monthNamesThai[int(t.Month())-1]) return string(r[:1]) + "." + string(r[len(r)-2:len(r)-1]) + "." } if abbr == 4 { return monthNamesThai[int(t.Month())-1] } return string([]rune(monthNamesThai[int(t.Month())-1])[:1]) } // localMonthsNameTibetan returns the Tibetan name of the month. func localMonthsNameTibetan(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTibetanAbbr[int(t.Month())-1] } if abbr == 5 { if t.Month() == 10 { return "\u0F66" } return "\u0F5F" } return monthNamesTibetan[int(t.Month())-1] } // localMonthsNameTigrinya returns the Tigrinya name of the month. func localMonthsNameTigrinya(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTigrinyaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTigrinya[int(t.Month())-1] } return string([]rune(monthNamesTigrinya[(t.Month() - 1)])[:1]) } // localMonthsNameTsonga returns the Tsonga name of the month. func localMonthsNameTsonga(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTsongaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTsonga[int(t.Month())-1] } return string([]rune(monthNamesTsonga[(t.Month() - 1)])[:1]) } // localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of // the month. func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string { if abbr == 5 { return "M" } return monthNamesTradMongolian[t.Month()-1] } // localMonthsNameTurkish returns the Turkish name of the month. func localMonthsNameTurkish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTurkishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTurkish[int(t.Month())-1] } return string([]rune(monthNamesTurkishAbbr[int(t.Month())-1])[:1]) } // localMonthsNameTurkmen returns the Turkmen name of the month. func localMonthsNameTurkmen(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTurkmenAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTurkmen[int(t.Month())-1] } return string([]rune(monthNamesTurkmenAbbr[int(t.Month())-1])[:1]) } // localMonthsNameUkrainian returns the Ukrainian name of the month. func localMonthsNameUkrainian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesUkrainianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesUkrainian[int(t.Month())-1] } return string([]rune(monthNamesUkrainian[int(t.Month())-1])[:1]) } // localMonthsNameUpperSorbian returns the Upper Sorbian name of the month. func localMonthsNameUpperSorbian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesUpperSorbianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesUpperSorbian[int(t.Month())-1] } return string([]rune(monthNamesUpperSorbian[int(t.Month())-1])[:1]) } // localMonthsNameUyghur returns the Uyghur name of the month. func localMonthsNameUyghur(t time.Time, abbr int) string { if abbr == 3 { return fmt.Sprintf("%d-\u0626\u0627\u064A", int(t.Month())) } if abbr == 4 { return monthNamesUyghur[int(t.Month())-1] } return string([]rune(monthNamesUyghur[int(t.Month())-1])[:1]) } // localMonthsNameUzbek returns the Uzbek name of the month. func localMonthsNameUzbek(t time.Time, abbr int) string { if abbr == 3 { return monthNamesUzbekAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesUzbek[int(t.Month())-1] } return string([]rune(monthNamesUzbek[int(t.Month())-1])[:1]) } // localMonthsNameUzbekCyrillic returns the Uzbek (Cyrillic) name of the month. func localMonthsNameUzbekCyrillic(t time.Time, abbr int) string { if abbr == 3 { return monthNamesTajikAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesTajik[int(t.Month())-1] } return string([]rune(monthNamesTajik[int(t.Month())-1])[:1]) } // localMonthsNameValencian returns the Valencian name of the month. func localMonthsNameValencian(t time.Time, abbr int) string { if abbr == 3 { return monthNamesValencianAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesValencian[int(t.Month())-1] } return string([]rune(monthNamesValencian[int(t.Month())-1])[:1]) } // localMonthsNameVenda returns the Venda name of the month. func localMonthsNameVenda(t time.Time, abbr int) string { if abbr == 3 { return monthNamesVendaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesVenda[int(t.Month())-1] } return string([]rune(monthNamesVenda[int(t.Month())-1])[:1]) } // localMonthsNameVietnamese returns the Vietnamese name of the month. func localMonthsNameVietnamese(t time.Time, abbr int) string { if abbr == 3 { return monthNamesVietnameseAbbr3[t.Month()-1] } if abbr == 5 { return monthNamesVietnameseAbbr5[t.Month()-1] } return monthNamesVietnamese[t.Month()-1] } // localMonthsNameWelsh returns the Welsh name of the month. func localMonthsNameWelsh(t time.Time, abbr int) string { if abbr == 3 { return monthNamesWelshAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesWelsh[int(t.Month())-1] } return monthNamesWelshAbbr[int(t.Month())-1][:1] } // localMonthsNameWolof returns the Wolof name of the month. func localMonthsNameWolof(t time.Time, abbr int) string { if abbr == 3 { return monthNamesWolofAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesWolof[int(t.Month())-1] } return monthNamesWolof[int(t.Month())-1][:1] } // localMonthsNameXhosa returns the Xhosa name of the month. func localMonthsNameXhosa(t time.Time, abbr int) string { if abbr == 3 { return monthNamesXhosaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesXhosa[int(t.Month())-1] } return "u" } // localMonthsNameYi returns the Yi name of the month. func localMonthsNameYi(t time.Time, abbr int) string { if abbr == 3 || abbr == 4 { return monthNamesYiSuffix[t.Month()-1] } return string([]rune(monthNamesYi[int(t.Month())-1])[:1]) } // localMonthsNameYiddish returns the Yiddish name of the month. func localMonthsNameYiddish(t time.Time, abbr int) string { if abbr == 3 { return monthNamesYiddishAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesYiddish[int(t.Month())-1] } return string([]rune(monthNamesYiddish[int(t.Month())-1])[:1]) } // localMonthsNameYoruba returns the Yoruba name of the month. func localMonthsNameYoruba(t time.Time, abbr int) string { if abbr == 3 { return monthNamesYorubaAbbr[int(t.Month())-1] } if abbr == 4 { return monthNamesYoruba[int(t.Month())-1] } return string([]rune(monthNamesYoruba[int(t.Month())-1])[:1]) } // localMonthsNameZulu returns the Zulu name of the month. func localMonthsNameZulu(t time.Time, abbr int) string { if abbr == 3 { return monthNamesZuluAbbr[t.Month()-1] } if abbr == 4 { return monthNamesZulu[int(t.Month())-1] } return monthNamesZuluAbbr[int(t.Month())-1][:1] } // localMonthsName return months name by supported language ID. func (nf *numberFormat) localMonthsName(abbr int) string { t := nf.t if nf.number < 1 { t = timeFromExcelTime(nf.number+2, nf.date1904) } if 1 <= nf.number && nf.number < 60 { t = timeFromExcelTime(nf.number+1, nf.date1904) } if languageInfo, ok := getSupportedLanguageInfo(nf.localCode); ok { return languageInfo.localMonth(t, abbr) } return localMonthsNameEnglish(t, abbr) } // dateAmPmHandler will be handling am/pm types tokens for a number format. func (nf *numberFormat) dateAmPmHandler(i int, token nfp.Token) { if nf.ap == "" { nextHours := nf.hoursNext(i) aps := strings.Split(nf.localAmPm(token.TValue), "/") nf.ap = aps[0] if nextHours >= 12 { nf.ap = aps[1] } } nf.result += nf.ap } // dateTimesHandler will be handling date and times types tokens for a number // format expression. func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) { if idx := inStrSlice(nfp.AmPm, strings.ToUpper(token.TValue), false); idx != -1 { nf.dateAmPmHandler(i, token) return } if strings.Contains(strings.ToUpper(token.TValue), "M") { m := int(nf.t.Month()) if nf.number < 2 { m = 1 } if 60 <= nf.number && nf.number < 61 { m = 2 } l := len(token.TValue) if l == 1 && nf.isMonthToken(i) { nf.result += strconv.Itoa(int(m)) return } if l == 2 && nf.isMonthToken(i) { nf.result += fmt.Sprintf("%02d", int(m)) return } if l == 3 { nf.result += nf.localMonthsName(3) return } if l == 4 || l > 5 { nf.result += nf.localMonthsName(4) return } if l == 5 { nf.result += nf.localMonthsName(5) return } } nf.yearsHandler(token) nf.daysHandler(token) nf.hoursHandler(i, token) nf.minutesHandler(token) nf.secondsHandler(token) } // eraYear convert time to the Japanese era years. func eraYear(t time.Time) (int, int) { i, year := 0, -1 for i = len(japaneseEraYears) - 1; i > 0; i-- { if y := japaneseEraYears[i]; t.After(y) { year = t.Year() - y.Year() + 1 break } } return i, year } // japaneseYearHandler handling the Japanease calendar years. func (nf *numberFormat) japaneseYearHandler(token nfp.Token, langInfo languageInfo) { if strings.Contains(strings.ToUpper(token.TValue), "G") { i, year := eraYear(nf.t) if year == -1 { return } nf.useGannen = langInfo.useGannen switch len(token.TValue) { case 1: nf.useGannen = false nf.result += japaneseEraSymbols[i] case 2: nf.result += japaneseEraNames[i][:3] default: nf.result += japaneseEraNames[i] } } if strings.Contains(strings.ToUpper(token.TValue), "E") { _, year := eraYear(nf.t) if year == -1 { nf.result += strconv.Itoa(nf.t.Year()) return } if year == 1 && nf.useGannen { nf.result += "\u5143" return } if len(token.TValue) == 1 && !nf.useGannen { nf.result += strconv.Itoa(year) return } if len(token.TValue) == 2 { nf.result += fmt.Sprintf("%02d", year) } } } // republicOfChinaYearHandler handling the Republic of China calendar years. func (nf *numberFormat) republicOfChinaYearHandler(token nfp.Token, langInfo languageInfo) { year := nf.t.Year() if nf.number < 2 { year = 1900 } if strings.Contains(strings.ToUpper(token.TValue), "G") { year = year - republicOfChinaYear.Year() + 1 if year == 1 { nf.useGannen = langInfo.useGannen } var name string if name = republicOfChinaEraName[0]; len(token.TValue) < 3 { name = republicOfChinaEraName[1] } if year < 0 { name += republicOfChinaEraName[2] } nf.result += name } if strings.Contains(strings.ToUpper(token.TValue), "E") { year = year - republicOfChinaYear.Year() + 1 if year < 0 { year = republicOfChinaYear.Year() - nf.t.Year() } if year == 1 && nf.useGannen { nf.result += "\u5143" return } if len(token.TValue) == 1 && !nf.useGannen { nf.result += strconv.Itoa(year) } } } // yearsHandler will be handling years in the date and times types tokens for a // number format expression. func (nf *numberFormat) yearsHandler(token nfp.Token) { langInfo, _ := getSupportedLanguageInfo(nf.localCode) year := nf.t.Year() if nf.number < 2 { year = 1900 } if strings.Contains(strings.ToUpper(token.TValue), "Y") { if nf.opts != nil && nf.opts.CultureInfo == CultureNameKoKR { year += 2333 } if len(token.TValue) <= 2 { nf.result += strconv.Itoa(year)[2:] return } nf.result += strconv.Itoa(year) return } if inStrSlice(langInfo.tags, "zh-TW", false) != -1 || nf.opts != nil && nf.opts.CultureInfo == CultureNameZhTW { nf.republicOfChinaYearHandler(token, langInfo) return } if inStrSlice(langInfo.tags, "ja-JP", false) != -1 || nf.opts != nil && nf.opts.CultureInfo == CultureNameJaJP { nf.japaneseYearHandler(token, langInfo) return } if strings.Contains(strings.ToUpper(token.TValue), "E") { nf.result += strconv.Itoa(year) return } } // daysHandler will be handling days in the date and times types tokens for a // number format expression. func (nf *numberFormat) daysHandler(token nfp.Token) { info, _ := getSupportedLanguageInfo(nf.localCode) l := len(token.TValue) weekdayNames, weekdayNamesAbbr := info.weekdayNames, info.weekdayNamesAbbr if len(weekdayNames) != 7 { weekdayNames = weekdayNamesEnglish } if len(weekdayNamesAbbr) != 7 { weekdayNamesAbbr = weekdayNamesEnglishAbbr } if strings.Contains(strings.ToUpper(token.TValue), "A") { if l == 3 { nf.result += weekdayNamesAbbr[nf.t.Weekday()] } if l > 3 { nf.result += weekdayNames[nf.t.Weekday()] } return } if strings.Contains(strings.ToUpper(token.TValue), "D") { d := nf.t.Day() if nf.number < 1 { d = 0 } if 1 <= nf.number && nf.number < 60 { d = timeFromExcelTime(nf.number+1, nf.date1904).Day() } if 60 <= nf.number && nf.number < 61 { d = 29 } switch l { case 1: nf.result += strconv.Itoa(d) case 2: nf.result += fmt.Sprintf("%02d", d) case 3: nf.result += weekdayNamesAbbr[nf.t.Weekday()] default: nf.result += weekdayNames[nf.t.Weekday()] } } } // hoursHandler will be handling hours in the date and times types tokens for a // number format expression. func (nf *numberFormat) hoursHandler(i int, token nfp.Token) { if nf.hours = strings.Contains(strings.ToUpper(token.TValue), "H"); nf.hours { h := nf.t.Hour() ap, ok := nf.apNext(i) if ok { nf.ap = ap[0] if h >= 12 { nf.ap = ap[1] } if h > 12 { h -= 12 } } if nf.ap != "" { if nf.hoursNext(i) == -1 && h > 12 { h -= 12 } if h == 0 { h = 12 } } switch len(token.TValue) { case 1: nf.result += strconv.Itoa(h) return default: nf.result += fmt.Sprintf("%02d", h) return } } } // minutesHandler will be handling minutes in the date and times types tokens // for a number format expression. func (nf *numberFormat) minutesHandler(token nfp.Token) { if strings.Contains(strings.ToUpper(token.TValue), "M") { nf.hours = false switch len(token.TValue) { case 1: nf.result += strconv.Itoa(nf.t.Minute()) return default: nf.result += fmt.Sprintf("%02d", nf.t.Minute()) } } } // secondsHandler will be handling seconds in the date and times types tokens // for a number format expression. func (nf *numberFormat) secondsHandler(token nfp.Token) { if nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S"); !nf.seconds { return } if len(token.TValue) == 1 { nf.result += strconv.Itoa(nf.t.Second()) return } nf.result += fmt.Sprintf("%02d", nf.t.Second()) } // elapsedDateTimesHandler will be handling elapsed date and times types tokens // for a number format expression. func (nf *numberFormat) elapsedDateTimesHandler(token nfp.Token) { if strings.Contains(strings.ToUpper(token.TValue), "H") { nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Hours())) return } if strings.Contains(strings.ToUpper(token.TValue), "M") { nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Minutes())) return } if strings.Contains(strings.ToUpper(token.TValue), "S") { nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Seconds())) return } } // hoursNext detects if a token of type hours exists after a given tokens list. func (nf *numberFormat) hoursNext(i int) int { tokens := nf.section[nf.sectionIdx].Items for idx := i + 1; idx < len(tokens); idx++ { if tokens[idx].TType == nfp.TokenTypeDateTimes { if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") { t := timeFromExcelTime(nf.number, false) return t.Hour() } } } return -1 } // apNext detects if a token of type AM/PM exists after a given tokens list. func (nf *numberFormat) apNext(i int) ([]string, bool) { tokens := nf.section[nf.sectionIdx].Items for idx := i + 1; idx < len(tokens); idx++ { if tokens[idx].TType == nfp.TokenTypeDateTimes { if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") { return nil, false } if i := inStrSlice(nfp.AmPm, tokens[idx].TValue, false); i != -1 { return strings.Split(nf.localAmPm(tokens[idx].TValue), "/"), true } } } return nil, false } // isMonthToken detects if the given token represents minutes, if no hours and // seconds tokens before the given token or not seconds after the given token, // the current token is a minutes token. func (nf *numberFormat) isMonthToken(i int) bool { tokens := nf.section[nf.sectionIdx].Items var timePrevious, secondsNext bool for idx := i - 1; idx >= 0; idx-- { if tokens[idx].TType == nfp.TokenTypeDateTimes { timePrevious = strings.ContainsAny(strings.ToUpper(tokens[idx].TValue), "HS") break } if tokens[idx].TType == nfp.TokenTypeElapsedDateTimes { timePrevious = true break } } for idx := i + 1; idx < len(tokens); idx++ { if tokens[idx].TType == nfp.TokenTypeDateTimes { secondsNext = strings.Contains(strings.ToUpper(tokens[idx].TValue), "S") break } } return !timePrevious && !secondsNext } // negativeHandler will be handling negative selection for a number format // expression. func (nf *numberFormat) negativeHandler() (result string) { for _, token := range nf.section[nf.sectionIdx].Items { if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral { return nf.value } if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 { return nf.value } } return nf.numberHandler() } // textHandler will be handling text selection for a number format expression. func (nf *numberFormat) textHandler() (result string) { for _, token := range nf.section[nf.sectionIdx].Items { if token.TType == nfp.TokenTypeLiteral { result += token.TValue } if token.TType == nfp.TokenTypeTextPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder { result += nf.value } } return result } // getValueSectionType returns its applicable number format expression section // based on the given value. func (nf *numberFormat) getValueSectionType(value string) (float64, string) { if nf.cellType != CellTypeNumber && nf.cellType != CellTypeDate { return 0, nfp.TokenSectionText } isNum, _, _ := isNumeric(value) if !isNum { return 0, nfp.TokenSectionText } number, _ := strconv.ParseFloat(value, 64) if number >= 0 { return number, nfp.TokenSectionPositive } var hasNeg bool for _, sec := range nf.section { if sec.Type == nfp.TokenSectionNegative { hasNeg = true } } if !hasNeg { nf.usePositive = true return number, nfp.TokenSectionPositive } return number, nfp.TokenSectionNegative } ================================================ FILE: numfmt_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" "github.com/xuri/nfp" ) func TestNumFmt(t *testing.T) { for _, item := range [][]string{ {"123", "general", "123"}, {"-123", ";general", "-123"}, {"123.456", "0.00\"\"", "123.46"}, {"123.456", "\"\";\"\"", ""}, {"123.456", "\"\"0.00foo", "123.46foo"}, {"123.456", "\"\"0.00\"foo\"", "123.46foo"}, {"12345678901", "General", "12345678901"}, {"43543.5448726851", "General", "43543.54487"}, {"-43543.5448726851", "General", "-43543.54487"}, {"1234567890.12345", "General", "1234567890"}, {"43528", "y", "19"}, {"43528", "Y", "19"}, {"43528", "yy", "19"}, {"43528", "YY", "19"}, {"43528", "yyy", "2019"}, {"43528", "YYY", "2019"}, {"43528", "yyyy", "2019"}, {"43528", "YYYY", "2019"}, {"43528", "yyyyy", "2019"}, {"43528", "YYYYY", "2019"}, {"43528", "m", "3"}, {"43528", "mm", "03"}, {"43528", "mmm", "Mar"}, {"43528", "mmmm", "March"}, {"43528", "mmmmm", "M"}, {"43528", "mmmmmm", "March"}, {"43528", "d", "4"}, {"43528", "dd", "04"}, {"43528", "ddd", "Mon"}, {"43528", "dddd", "Monday"}, {"43528", "h", "0"}, {"43528", "hh", "00"}, {"43528", "hhh", "00"}, {"43543.544872685183", "hhmm", "1304"}, {"43543.544872685183", "mmhhmmmm", "0313March"}, {"43543.544872685183", "mm hh mm mm", "03 13 04 03"}, {"43543.544872685183", "mm hh m m", "03 13 4 3"}, {"43543.544872685183", "m s", "4 37"}, {"43528", "[h]", "1044672"}, {"43528", "[m]", "62680320"}, {"43528", "s", "0"}, {"43528", "ss", "00"}, {"43528", "[s]", "3760819200"}, {"43543.544872685183", "h:mm:ss AM/PM", "1:04:37 PM"}, {"43543.544872685183", "AM/PM h:mm:ss", "PM 1:04:37"}, {"43543.086539351854", "hh:mm:ss AM/PM", "02:04:37 AM"}, {"43543.086539351854", "AM/PM hh:mm:ss", "AM 02:04:37"}, {"43543.086539351854", "AM/PM hh:mm:ss a/p", "AM 02:04:37 a"}, {"0.609375", "[HH]:mm:ss", "14:37:30"}, {"43528", "YYYY", "2019"}, {"43528", "", "43528"}, {"43528.2123", "YYYY-MM-DD hh:mm:ss", "2019-03-04 05:05:43"}, {"43528.2123", "YYYY-MM-DD hh:mm:ss;YYYY-MM-DD hh:mm:ss", "2019-03-04 05:05:43"}, {"43528.2123", "M/D/YYYY h:m:s", "3/4/2019 5:5:43"}, {"43528.003958333335", "m/d/yyyy h:m:s", "3/4/2019 0:5:42"}, {"43528.003958333335", "M/D/YYYY h:mm:s", "3/4/2019 0:05:42"}, {"0.64583333333333337", "h:mm:ss am/pm", "3:30:00 pm"}, {"43528.003958333335", "h:mm", "0:05"}, {"6.9444444444444444E-5", "h:m", "0:0"}, {"6.9444444444444444E-5", "h:mm", "0:00"}, {"6.9444444444444444E-5", "h:m", "0:0"}, {"0.50070601851851848", "h:m", "12:1"}, {"0.97952546296296295", "h:m", "23:30"}, {"43528", "mmmm", "March"}, {"43528", "dddd", "Monday"}, {"0", ";;;", ""}, {"0", "0%", "0%"}, {"0", "0.0%", "0.0%"}, {"0", "0.00%", "0.00%"}, {"0", "[$-zh-TW]yyyy\"年\"m\"月\"d\"日\";@", "1900年1月0日"}, {"60", "yyyy-mm-dd;@", "1900-02-29"}, {"43528", "[$-409]MM/DD/YYYY", "03/04/2019"}, {"43528", "[$-409]MM/DD/YYYY am/pm", "03/04/2019 AM"}, {"43528", "[$-111]MM/DD/YYYY", "43528"}, {"43528", "[$US-409]MM/DD/YYYY", "US03/04/2019"}, {"43543.586539351854", "AM/PM h h:mm", "PM 14 2:04"}, {"45186", "DD.MM.YYYY", "17.09.2023"}, {"text", "AM/PM h h:mm", "text"}, {"43466.189571759256", "[$-404]aaa;@", "週二"}, {"43466.189571759256", "[$-404]aaaa;@", "星期二"}, {"43466.189571759256", "[$-zh-TW]aaa;@", "週二"}, {"43466.189571759256", "[$-zh-TW]aaaa;@", "星期二"}, {"43466.189571759256", "[$-804]aaa;@", "周二"}, {"43466.189571759256", "[$-804]aaaa;@", "星期二"}, {"43466.189571759256", "[$-0804]aaa;@", "周二"}, {"43466.189571759256", "[$-0804]aaaa;@", "星期二"}, {"43466.189571759256", "[$-zh-CN]aaa;@", "周二"}, {"43466.189571759256", "[$-zh-CN]aaaa;@", "星期二"}, {"43466.189571759256", "[$-435]aaa;@", "Bi."}, {"43466.189571759256", "[$-435]aaaa;@", "ULwesibili"}, {"43466.189571759256", "[$-0435]aaa;@", "Bi."}, {"43466.189571759256", "[$-0435]aaaa;@", "ULwesibili"}, {"43466.189571759256", "[$-zu-ZA]aaa;@", "Bi."}, {"43466.189571759256", "[$-zu-ZA]aaaa;@", "ULwesibili"}, {"43466.189571759256", "[$-404]ddd;@", "週二"}, {"43466.189571759256", "[$-404]dddd;@", "星期二"}, {"43466.189571759256", "[$-0404]ddd;@", "週二"}, {"43466.189571759256", "[$-0404]dddd;@", "星期二"}, {"43466.189571759256", "[$-zh-TW]ddd;@", "週二"}, {"43466.189571759256", "[$-zh-TW]dddd;@", "星期二"}, {"43466.189571759256", "[$-804]ddd;@", "周二"}, {"43466.189571759256", "[$-804]dddd;@", "星期二"}, {"43466.189571759256", "[$-0804]ddd;@", "周二"}, {"43466.189571759256", "[$-0804]dddd;@", "星期二"}, {"43466.189571759256", "[$-zh-CN]ddd;@", "周二"}, {"43466.189571759256", "[$-zh-CN]dddd;@", "星期二"}, {"43466.189571759256", "[$-435]ddd;@", "Bi."}, {"43466.189571759256", "[$-435]dddd;@", "ULwesibili"}, {"43466.189571759256", "[$-0435]ddd;@", "Bi."}, {"43466.189571759256", "[$-0435]dddd;@", "ULwesibili"}, {"43466.189571759256", "[$-zu-ZA]ddd;@", "Bi."}, {"43466.189571759256", "[$-zu-ZA]dddd;@", "ULwesibili"}, {"44562.189571759256", "[$-36]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"}, {"44562.189571759256", "[$-36]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"}, {"44562.189571759256", "[$-36]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."}, {"44682.18957170139", "[$-36]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"}, {"44682.18957170139", "[$-36]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."}, {"44682.18957170139", "[$-36]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"}, {"44562.189571759256", "[$-036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"}, {"44562.189571759256", "[$-036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"}, {"44562.189571759256", "[$-036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."}, {"44682.18957170139", "[$-036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"}, {"44682.18957170139", "[$-036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."}, {"44682.18957170139", "[$-036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"}, {"44562.189571759256", "[$-0036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"}, {"44562.189571759256", "[$-0036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"}, {"44562.189571759256", "[$-0036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."}, {"44682.18957170139", "[$-0036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"}, {"44682.18957170139", "[$-0036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."}, {"44682.18957170139", "[$-0036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"}, {"44562.189571759256", "[$-af]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"}, {"44562.189571759256", "[$-af]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"}, {"44562.189571759256", "[$-af]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."}, {"44682.18957170139", "[$-af]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"}, {"44682.18957170139", "[$-af]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."}, {"44682.18957170139", "[$-af]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"}, {"44562.189571759256", "[$-436]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"}, {"44562.189571759256", "[$-436]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"}, {"44562.189571759256", "[$-436]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."}, {"44682.18957170139", "[$-436]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"}, {"44682.18957170139", "[$-436]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."}, {"44682.18957170139", "[$-436]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"}, {"44562.189571759256", "[$-1C]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-1C]mmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-1C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-1C]mmmmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."}, {"43543.503206018519", "[$-1C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 m.d."}, {"43543.503206018519", "[$-1C]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 m.d. mar"}, {"43543.503206018519", "[$-1C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 m.d. mar"}, {"43543.503206018519", "[$-1C]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 m.d. e martë"}, {"44562.189571759256", "[$-41C]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-41C]mmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-41C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 p.d."}, {"44562.189571759256", "[$-41C]mmmmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."}, {"43543.503206018519", "[$-41C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 m.d."}, {"43543.503206018519", "[$-41C]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 m.d. mar"}, {"43543.503206018519", "[$-41C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 m.d. mar"}, {"43543.503206018519", "[$-41C]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 m.d. e martë"}, {"44562.189571759256", "[$-84]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 vorm."}, {"44562.189571759256", "[$-84]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 vorm."}, {"44562.189571759256", "[$-84]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 vorm."}, {"44562.189571759256", "[$-84]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 vorm."}, {"43543.503206018519", "[$-84]mmm dd yyyy h:mm AM/PM", "Mär 19 2019 12:04 nam."}, {"43543.503206018519", "[$-84]mmmm dd yyyy h:mm AM/PM aaa", "März 19 2019 12:04 nam. Zi."}, {"43543.503206018519", "[$-84]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 nam. Zi."}, {"43543.503206018519", "[$-84]mmmmmm dd yyyy h:mm AM/PM dddd", "März 19 2019 12:04 nam. Ziischtig"}, {"44562.189571759256", "[$-484]mmm dd yyyy h:mm AM/PM", "Jän. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-484]mmmm dd yyyy h:mm AM/PM", "Jänner 01 2022 4:32 AM"}, {"44562.189571759256", "[$-484]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-484]mmmmmm dd yyyy h:mm AM/PM", "Jänner 01 2022 4:32 AM"}, {"43543.503206018519", "[$-484]mmm dd yyyy h:mm AM/PM", "März 19 2019 12:04 PM"}, {"43543.503206018519", "[$-484]mmmm dd yyyy h:mm AM/PM aaa", "März 19 2019 12:04 PM Zi."}, {"43543.503206018519", "[$-484]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Zi."}, {"43543.503206018519", "[$-484]mmmmmm dd yyyy h:mm AM/PM dddd", "März 19 2019 12:04 PM Zischti"}, {"44562.189571759256", "[$-5E]mmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-5E]mmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-5E]mmmmm dd yyyy h:mm AM/PM", "\u1303 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-5E]mmmmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"}, {"43543.503206018519", "[$-5E]mmm dd yyyy h:mm AM/PM", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275"}, {"43543.503206018519", "[$-5E]mmmm dd yyyy h:mm AM/PM aaa", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"}, {"43543.503206018519", "[$-5E]mmmmm dd yyyy h:mm AM/PM ddd", "\u121B 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"}, {"43543.503206018519", "[$-5E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230\u129E"}, {"44562.189571759256", "[$-45E]mmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-45E]mmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-45E]mmmmm dd yyyy h:mm AM/PM", "\u1303 01 2022 4:32 \u1325\u12CB\u1275"}, {"44562.189571759256", "[$-45E]mmmmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"}, {"43543.503206018519", "[$-45E]mmm dd yyyy h:mm AM/PM", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275"}, {"43543.503206018519", "[$-45E]mmmm dd yyyy h:mm AM/PM aaa", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"}, {"43543.503206018519", "[$-45E]mmmmm dd yyyy h:mm AM/PM ddd", "\u121B 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"}, {"43543.503206018519", "[$-45E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230\u129E"}, {"44562.189571759256", "[$-1]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-1401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-3C01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3C01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3C01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3C01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-3C01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-3C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-c01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-c01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-c01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-c01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-c01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-c01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-c01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-c01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-801]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-801]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-801]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-801]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-801]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-801]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-2C01]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2C01]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2C01]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2C01]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-2C01]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-2C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-3401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-3401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-3401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-3001]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3001]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3001]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3001]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-3001]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-3001]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-1001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-1801]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1801]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1801]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1801]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1801]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1801]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-2001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-2001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-2001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-4001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-4001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-4001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-4001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-4001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-4001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-4001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-4001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43466.189571759256", "[$-404]g\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5E74\u0031\u6708\u0031\u65E5"}, {"43466.189571759256", "[$-404]e\"年\"m\"月\"d\"日\";@", "\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"}, {"43466.189571759256", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"}, {"43466.189571759256", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"}, {"43466.189571759256", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"}, {"43466.189571759256", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"}, {"4385.5083333333332", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"}, {"4385.5083333333332", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"}, {"4385.5083333333332", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"}, {"4385.5083333333332", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"}, {"123", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"}, {"123", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"}, {"123", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"}, {"123", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"}, {"44562.189571759256", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1010401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1010401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1010401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1010401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-2801]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2801]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2801]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2801]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-2801]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-2801]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-1C01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1C01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1C01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-1C01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-1C01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-1C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-1C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-3801]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3801]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3801]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-3801]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-3801]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-3801]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-3801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-2401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-2401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-2401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-2401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-2401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-2B]mmm dd yyyy h:mm AM/PM", "\u0540\u0576\u057E 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2B]mmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2B]mmmmm dd yyyy h:mm AM/PM", "\u0540 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2B]mmmmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"}, {"43543.503206018519", "[$-2B]mmm dd yyyy h:mm AM/PM", "\u0544\u0580\u057F 19 2019 12:04 PM"}, {"43543.503206018519", "[$-2B]mmmm dd yyyy h:mm AM/PM aaa", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0584"}, {"43543.503206018519", "[$-2B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0544 19 2019 12:04 PM \u0535\u0580\u0584"}, {"43543.503206018519", "[$-2B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B"}, {"44562.189571759256", "[$-42B]mmm dd yyyy h:mm AM/PM", "\u0540\u0576\u057E 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42B]mmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42B]mmmmm dd yyyy h:mm AM/PM", "\u0540 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42B]mmmmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"}, {"43543.503206018519", "[$-42B]mmm dd yyyy h:mm AM/PM", "\u0544\u0580\u057F 19 2019 12:04 PM"}, {"43543.503206018519", "[$-42B]mmmm dd yyyy h:mm AM/PM aaa", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0584"}, {"43543.503206018519", "[$-42B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0544 19 2019 12:04 PM \u0535\u0580\u0584"}, {"43543.503206018519", "[$-42B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B"}, {"44562.189571759256", "[$-4D]mmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-4D]mmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-4D]mmmmm dd yyyy h:mm AM/PM", "\u099C 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-4D]mmmmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"43543.503206018519", "[$-4D]mmm dd yyyy h:mm AM/PM", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF"}, {"43543.503206018519", "[$-4D]mmmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-4D]mmmmm dd yyyy h:mm AM/PM ddd", "\u09AE 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-4D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0"}, {"44562.189571759256", "[$-44D]mmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-44D]mmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-44D]mmmmm dd yyyy h:mm AM/PM", "\u099C 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"44562.189571759256", "[$-44D]mmmmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"}, {"43543.503206018519", "[$-44D]mmm dd yyyy h:mm AM/PM", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF"}, {"43543.503206018519", "[$-44D]mmmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-44D]mmmmm dd yyyy h:mm AM/PM ddd", "\u09AE 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-44D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0"}, {"44562.189571759256", "[$-742C]mmm dd yyyy h:mm AM/PM", "\u0408\u0430\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-742C]mmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-742C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-742C]mmmmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-742C]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-742C]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0427\u0430"}, {"43543.503206018519", "[$-742C]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0427\u0430"}, {"43543.503206018519", "[$-742C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B"}, {"44562.189571759256", "[$-82C]mmm dd yyyy h:mm AM/PM", "\u0408\u0430\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82C]mmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82C]mmmmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-82C]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-82C]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0427\u0430"}, {"43543.503206018519", "[$-82C]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0427\u0430"}, {"43543.503206018519", "[$-82C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B"}, {"44562.189571759256", "[$-2C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-2C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-2C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-2C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-2C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"}, {"44562.189571759256", "[$-782C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-782C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-782C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-782C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-782C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-782C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-782C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-782C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"}, {"44562.189571759256", "[$-42C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-42C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-42C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-42C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."}, {"43543.503206018519", "[$-42C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"}, {"43543.503206018519", "[$-45]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-45]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-45]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"}, {"43543.503206018519", "[$-845]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-845]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-845]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"}, {"43543.503206018519", "[$-445]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-445]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."}, {"43543.503206018519", "[$-445]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"}, {"44562.189571759256", "[$-6D]mmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6D]mmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6D]mmmmm dd yyyy h:mm AM/PM", "\u0493 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6D]mmmmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-6D]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-6D]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0448"}, {"43543.503206018519", "[$-6D]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0428\u0448"}, {"43543.503206018519", "[$-6D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0438\u0448\u04D9\u043C\u0431\u0435"}, {"44562.189571759256", "[$-46D]mmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46D]mmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46D]mmmmm dd yyyy h:mm AM/PM", "\u0493 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46D]mmmmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-46D]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-46D]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0448"}, {"43543.503206018519", "[$-46D]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0428\u0448"}, {"43543.503206018519", "[$-46D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0438\u0448\u04D9\u043C\u0431\u0435"}, {"44562.189571759256", "[$-2D]mmm dd yyyy h:mm AM/PM", "urt. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2D]mmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2D]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2D]mmmmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"}, {"43543.503206018519", "[$-2D]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-2D]mmmm dd yyyy h:mm AM/PM aaa", "martxoa 19 2019 12:04 PM ar."}, {"43543.503206018519", "[$-2D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ar."}, {"43543.503206018519", "[$-2D]mmmmmm dd yyyy h:mm AM/PM dddd", "martxoa 19 2019 12:04 PM asteartea"}, {"44562.189571759256", "[$-42D]mmm dd yyyy h:mm AM/PM", "urt. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42D]mmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42D]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42D]mmmmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"}, {"43543.503206018519", "[$-42D]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-42D]mmmm dd yyyy h:mm AM/PM aaa", "martxoa 19 2019 12:04 PM ar."}, {"43543.503206018519", "[$-42D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ar."}, {"43543.503206018519", "[$-42D]mmmmmm dd yyyy h:mm AM/PM dddd", "martxoa 19 2019 12:04 PM asteartea"}, {"44562.189571759256", "[$-23]mmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437 01 2022 4:32 AM"}, {"44562.189571759256", "[$-23]mmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-23]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"}, {"44562.189571759256", "[$-23]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"}, {"43543.503206018519", "[$-23]mmm dd yyyy h:mm AM/PM", "\u0441\u0430\u043A 19 2019 12:04 PM"}, {"43543.503206018519", "[$-23]mmmm dd yyyy h:mm AM/PM aaa", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442"}, {"43543.503206018519", "[$-23]mmmmm dd yyyy h:mm AM/PM ddd", "\u0441 19 2019 12:04 PM \u0430\u045E\u0442"}, {"43543.503206018519", "[$-23]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-423]mmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437 01 2022 4:32 AM"}, {"44562.189571759256", "[$-423]mmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-423]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"}, {"44562.189571759256", "[$-423]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"}, {"43543.503206018519", "[$-423]mmm dd yyyy h:mm AM/PM", "\u0441\u0430\u043A 19 2019 12:04 PM"}, {"43543.503206018519", "[$-423]mmmm dd yyyy h:mm AM/PM aaa", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442"}, {"43543.503206018519", "[$-423]mmmmm dd yyyy h:mm AM/PM ddd", "\u0441 19 2019 12:04 PM \u0430\u045E\u0442"}, {"43543.503206018519", "[$-423]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-641A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-641A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-641A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-641A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-641A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-641A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-641A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-641A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-201A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-201A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-201A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-201A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-201A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-201A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-201A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-201A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-681A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-681A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-681A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-681A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-681A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-681A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-681A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-681A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"}, {"44562.189571759256", "[$-781A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-781A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-781A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-781A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-781A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-781A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-781A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-781A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"}, {"44562.189571759256", "[$-141A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-141A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-141A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-141A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-141A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-141A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-141A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"}, {"43543.503206018519", "[$-141A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"}, {"44562.189571759256", "[$-7E]mmm dd yyyy h:mm AM/PM", "Gen. 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-7E]mmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-7E]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-7E]mmmmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."}, {"43543.503206018519", "[$-7E]mmm dd yyyy h:mm AM/PM", "Meur. 19 2019 12:04 G.M."}, {"43543.503206018519", "[$-7E]mmmm dd yyyy h:mm AM/PM aaa", "Meurzh 19 2019 12:04 G.M. Meu."}, {"43543.503206018519", "[$-7E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 G.M. Meu."}, {"43543.503206018519", "[$-7E]mmmmmm dd yyyy h:mm AM/PM dddd", "Meurzh 19 2019 12:04 G.M. Meurzh"}, {"44562.189571759256", "[$-47E]mmm dd yyyy h:mm AM/PM", "Gen. 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-47E]mmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-47E]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-47E]mmmmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."}, {"43543.503206018519", "[$-47E]mmm dd yyyy h:mm AM/PM", "Meur. 19 2019 12:04 G.M."}, {"43543.503206018519", "[$-47E]mmmm dd yyyy h:mm AM/PM aaa", "Meurzh 19 2019 12:04 G.M. Meu."}, {"43543.503206018519", "[$-47E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 G.M. Meu."}, {"43543.503206018519", "[$-47E]mmmmmm dd yyyy h:mm AM/PM dddd", "Meurzh 19 2019 12:04 G.M. Meurzh"}, {"44562.189571759256", "[$-2]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-2]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"}, {"43543.503206018519", "[$-2]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-2]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442"}, {"43543.503206018519", "[$-2]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0432\u0442"}, {"43543.503206018519", "[$-2]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"44562.189571759256", "[$-402]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443 01 2022 4:32 AM"}, {"44562.189571759256", "[$-402]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"}, {"44562.189571759256", "[$-402]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-402]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"}, {"43543.503206018519", "[$-402]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-402]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442"}, {"43543.503206018519", "[$-402]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0432\u0442"}, {"43543.503206018519", "[$-402]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"44562.189571759256", "[$-55]mmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-55]mmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-55]mmmmm dd yyyy h:mm AM/PM", "\u1007 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-55]mmmmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"43543.503206018519", "[$-55]mmm dd yyyy h:mm AM/PM", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031"}, {"43543.503206018519", "[$-55]mmmm dd yyyy h:mm AM/PM aaa", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"43543.503206018519", "[$-55]mmmmm dd yyyy h:mm AM/PM ddd", "\u1019 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"43543.503206018519", "[$-55]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"44562.189571759256", "[$-455]mmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-455]mmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-455]mmmmm dd yyyy h:mm AM/PM", "\u1007 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"44562.189571759256", "[$-455]mmmmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"}, {"43543.503206018519", "[$-455]mmm dd yyyy h:mm AM/PM", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031"}, {"43543.503206018519", "[$-455]mmmm dd yyyy h:mm AM/PM aaa", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"43543.503206018519", "[$-455]mmmmm dd yyyy h:mm AM/PM ddd", "\u1019 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"43543.503206018519", "[$-455]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"}, {"44562.189571759256", "[$-3]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-3]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-3]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-3]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-3]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-3]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-3]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-3]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"}, {"44562.189571759256", "[$-403]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-403]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-403]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-403]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-403]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-403]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-403]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-403]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"}, {"44562.189571759256", "[$-45F]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-45F]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-45F]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"}, {"44562.189571759256", "[$-45F]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"}, {"43543.503206018519", "[$-45F]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"}, {"43543.503206018519", "[$-45F]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-45F]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"43543.503206018519", "[$-45F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"}, {"44562.189571759256", "[$-92]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-92]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-92]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-92]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"43543.503206018519", "[$-92]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"}, {"43543.503206018519", "[$-92]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-92]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-92]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"44562.189571759256", "[$-7C92]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-7C92]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-7C92]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-7C92]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"43543.503206018519", "[$-7C92]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"}, {"43543.503206018519", "[$-7C92]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-7C92]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-7C92]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"44562.189571759256", "[$-492]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-492]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-492]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"}, {"44562.189571759256", "[$-492]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"}, {"43543.503206018519", "[$-492]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"}, {"43543.503206018519", "[$-492]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-492]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"43543.503206018519", "[$-492]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"}, {"44562.189571759256", "[$-5C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"43543.503206018519", "[$-5C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"}, {"43543.503206018519", "[$-5C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-5C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-5C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"}, {"44562.189571759256", "[$-7C5C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C5C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C5C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-7C5C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-7C5C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"}, {"44562.189571759256", "[$-45C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"}, {"43543.503206018519", "[$-45C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"}, {"43543.503206018519", "[$-45C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-45C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"}, {"43543.503206018519", "[$-45C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"}, {"43543.503206018519", "[$-4]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-4]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-4]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-7804]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-7804]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-7804]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-804]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 周二"}, {"43543.503206018519", "[$-804]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 周二"}, {"43543.503206018519", "[$-804]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-1004]mmm dd yyyy h:mm AM/PM aaa", "三月 19 2019 12:04 下午 周二"}, {"43543.503206018519", "[$-1004]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 周二"}, {"43543.503206018519", "[$-1004]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-7C04]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-7C04]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-7C04]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-C04]mmm dd yyyy h:mm AM/PM aaa", "三月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-C04]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-C04]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-1404]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-1404]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-1404]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"}, {"43543.503206018519", "[$-404]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-404]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"}, {"43543.503206018519", "[$-404]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"}, {"44562.189571759256", "[$-83]mmm dd yyyy h:mm AM/PM", "ghje 01 2022 4:32 AM"}, {"44593.189571759256", "[$-83]mmm dd yyyy h:mm AM/PM", "ferr 01 2022 4:32 AM"}, {"44621.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "marz 01 2022 4:32 AM"}, {"44652.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "apri 01 2022 4:32 AM"}, {"44682.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "magh 01 2022 4:32 AM"}, {"44713.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "ghju 01 2022 4:32 AM"}, {"44743.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "lugl 01 2022 4:32 AM"}, {"44774.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "aost 01 2022 4:32 AM"}, {"44805.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "sett 01 2022 4:32 AM"}, {"44835.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "otto 01 2022 4:32 AM"}, {"44866.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "nuve 01 2022 4:32 AM"}, {"44896.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "dice 01 2022 4:32 AM"}, {"44562.189571759256", "[$-83]mmmm dd yyyy h:mm AM/PM", "ghjennaghju 01 2022 4:32 AM"}, {"44593.189571759256", "[$-83]mmmm dd yyyy h:mm AM/PM", "ferraghju 01 2022 4:32 AM"}, {"44621.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "marzu 01 2022 4:32 AM"}, {"44652.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "aprile 01 2022 4:32 AM"}, {"44682.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "maghju 01 2022 4:32 AM"}, {"44713.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "ghjunghju 01 2022 4:32 AM"}, {"44743.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "lugliu 01 2022 4:32 AM"}, {"44774.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "aostu 01 2022 4:32 AM"}, {"44805.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "settembre 01 2022 4:32 AM"}, {"44835.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "ottobre 01 2022 4:32 AM"}, {"44866.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "nuvembre 01 2022 4:32 AM"}, {"44896.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "dicembre 01 2022 4:32 AM"}, {"44562.189571759256", "[$-83]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44593.189571759256", "[$-83]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44743.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"}, {"44774.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM sab."}, {"44866.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM mar."}, {"44896.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM ghjovi"}, {"44562.189571759256", "[$-483]mmm dd yyyy h:mm AM/PM", "ghje 01 2022 4:32 AM"}, {"44593.189571759256", "[$-483]mmm dd yyyy h:mm AM/PM", "ferr 01 2022 4:32 AM"}, {"44621.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "marz 01 2022 4:32 AM"}, {"44652.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "apri 01 2022 4:32 AM"}, {"44682.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "magh 01 2022 4:32 AM"}, {"44713.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "ghju 01 2022 4:32 AM"}, {"44743.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "lugl 01 2022 4:32 AM"}, {"44774.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "aost 01 2022 4:32 AM"}, {"44805.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "sett 01 2022 4:32 AM"}, {"44835.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "otto 01 2022 4:32 AM"}, {"44866.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "nuve 01 2022 4:32 AM"}, {"44896.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "dice 01 2022 4:32 AM"}, {"44562.189571759256", "[$-483]mmmm dd yyyy h:mm AM/PM", "ghjennaghju 01 2022 4:32 AM"}, {"44593.189571759256", "[$-483]mmmm dd yyyy h:mm AM/PM", "ferraghju 01 2022 4:32 AM"}, {"44621.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "marzu 01 2022 4:32 AM"}, {"44652.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "aprile 01 2022 4:32 AM"}, {"44682.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "maghju 01 2022 4:32 AM"}, {"44713.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "ghjunghju 01 2022 4:32 AM"}, {"44743.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "lugliu 01 2022 4:32 AM"}, {"44774.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "aostu 01 2022 4:32 AM"}, {"44805.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "settembre 01 2022 4:32 AM"}, {"44835.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "ottobre 01 2022 4:32 AM"}, {"44866.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "nuvembre 01 2022 4:32 AM"}, {"44896.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "dicembre 01 2022 4:32 AM"}, {"44562.189571759256", "[$-483]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44593.189571759256", "[$-483]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44743.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"}, {"44774.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM sab."}, {"44866.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM mar."}, {"44896.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM ghjovi"}, {"44562.189571759256", "[$-1A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1A]mmm dd yyyy h:mm AM/PM", "vlj 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"}, {"44866.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"}, {"44896.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"}, {"44866.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"}, {"44896.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"}, {"44866.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"}, {"44896.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"}, {"44562.189571759256", "[$-41A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41A]mmm dd yyyy h:mm AM/PM", "vlj 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"}, {"44866.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"}, {"44896.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"}, {"44866.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"}, {"44896.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"}, {"44866.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"}, {"44896.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"}, {"44562.189571759256", "[$-101A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"}, {"44593.189571759256", "[$-101A]mmm dd yyyy h:mm AM/PM", "velj 01 2022 4:32 AM"}, {"44621.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"}, {"44652.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"}, {"44682.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"}, {"44743.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"}, {"44774.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"}, {"44805.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"}, {"44835.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"}, {"44866.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"}, {"44896.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-101A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"}, {"44593.189571759256", "[$-101A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"}, {"44621.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"}, {"44652.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"}, {"44682.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"}, {"44743.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"}, {"44774.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"}, {"44805.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"}, {"44835.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"}, {"44866.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"}, {"44896.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"}, {"44562.189571759256", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44593.189571759256", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"}, {"44621.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44652.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44682.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44713.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"}, {"44743.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44774.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"}, {"44805.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"}, {"44835.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"}, {"44866.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"}, {"44896.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"}, {"44562.189571759256", "[$-5]mmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 dop."}, {"44593.189571759256", "[$-5]mmm dd yyyy h:mm AM/PM", "II 01 2022 4:32 dop."}, {"44621.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "III 01 2022 4:32 dop."}, {"44652.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "IV 01 2022 4:32 dop."}, {"44682.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "V 01 2022 4:32 dop."}, {"44713.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VI 01 2022 4:32 dop."}, {"44743.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VII 01 2022 4:32 dop."}, {"44774.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VIII 01 2022 4:32 dop."}, {"44805.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "IX 01 2022 4:32 dop."}, {"44835.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 dop."}, {"44866.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "XI 01 2022 4:32 dop."}, {"44896.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "XII 01 2022 4:32 dop."}, {"44562.189571759256", "[$-5]mmmm dd yyyy h:mm AM/PM", "leden 01 2022 4:32 dop."}, {"44593.189571759256", "[$-5]mmmm dd yyyy h:mm AM/PM", "únor 01 2022 4:32 dop."}, {"44621.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "březen 01 2022 4:32 dop."}, {"44652.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "duben 01 2022 4:32 dop."}, {"44682.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "květen 01 2022 4:32 dop."}, {"44713.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "červen 01 2022 4:32 dop."}, {"44743.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "červenec 01 2022 4:32 dop."}, {"44774.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "srpen 01 2022 4:32 dop."}, {"44805.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "září 01 2022 4:32 dop."}, {"44835.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "říjen 01 2022 4:32 dop."}, {"44866.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 dop."}, {"44896.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "prosinec 01 2022 4:32 dop."}, {"44562.189571759256", "[$-5]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 dop."}, {"44593.189571759256", "[$-5]mmmmm dd yyyy h:mm AM/PM", "ú 01 2022 4:32 dop."}, {"44621.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "b 01 2022 4:32 dop."}, {"44652.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "d 01 2022 4:32 dop."}, {"44682.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 dop."}, {"44713.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."}, {"44743.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."}, {"44774.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 dop."}, {"44805.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "z 01 2022 4:32 dop."}, {"44835.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM aaa", "ř 01 2022 4:32 dop. so"}, {"44866.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM ddd", "l 01 2022 4:32 dop. út"}, {"44896.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 dop. čtvrtek"}, {"44562.189571759256", "[$-405]mmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 dop."}, {"44593.189571759256", "[$-405]mmm dd yyyy h:mm AM/PM", "II 01 2022 4:32 dop."}, {"44621.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "III 01 2022 4:32 dop."}, {"44652.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "IV 01 2022 4:32 dop."}, {"44682.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "V 01 2022 4:32 dop."}, {"44713.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VI 01 2022 4:32 dop."}, {"44743.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VII 01 2022 4:32 dop."}, {"44774.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VIII 01 2022 4:32 dop."}, {"44805.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "IX 01 2022 4:32 dop."}, {"44835.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 dop."}, {"44866.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "XI 01 2022 4:32 dop."}, {"44896.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "XII 01 2022 4:32 dop."}, {"44562.189571759256", "[$-405]mmmm dd yyyy h:mm AM/PM", "leden 01 2022 4:32 dop."}, {"44593.189571759256", "[$-405]mmmm dd yyyy h:mm AM/PM", "únor 01 2022 4:32 dop."}, {"44621.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "březen 01 2022 4:32 dop."}, {"44652.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "duben 01 2022 4:32 dop."}, {"44682.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "květen 01 2022 4:32 dop."}, {"44713.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "červen 01 2022 4:32 dop."}, {"44743.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "červenec 01 2022 4:32 dop."}, {"44774.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "srpen 01 2022 4:32 dop."}, {"44805.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "září 01 2022 4:32 dop."}, {"44835.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "říjen 01 2022 4:32 dop."}, {"44866.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 dop."}, {"44896.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "prosinec 01 2022 4:32 dop."}, {"44562.189571759256", "[$-405]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 dop."}, {"44593.189571759256", "[$-405]mmmmm dd yyyy h:mm AM/PM", "ú 01 2022 4:32 dop."}, {"44621.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "b 01 2022 4:32 dop."}, {"44652.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "d 01 2022 4:32 dop."}, {"44682.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 dop."}, {"44713.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."}, {"44743.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."}, {"44774.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 dop."}, {"44805.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "z 01 2022 4:32 dop."}, {"44835.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM aaa", "ř 01 2022 4:32 dop. so"}, {"44866.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM ddd", "l 01 2022 4:32 dop. út"}, {"44896.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 dop. čtvrtek"}, {"44562.189571759256", "[$-6]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-6]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "mar 01 2022 4:32 AM"}, {"44652.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"}, {"44682.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"}, {"44805.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44593.189571759256", "[$-6]mmmm dd yyyy h:mm AM/PM", "februar 01 2022 4:32 AM"}, {"44621.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "marts 01 2022 4:32 AM"}, {"44652.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"}, {"44682.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"}, {"44774.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "august 01 2022 4:32 AM"}, {"44805.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"}, {"44835.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"}, {"44866.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"}, {"44896.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44593.189571759256", "[$-6]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44743.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44774.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM lø"}, {"44866.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM ti"}, {"44896.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM torsdag"}, {"44562.189571759256", "[$-406]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-406]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "mar 01 2022 4:32 AM"}, {"44652.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"}, {"44682.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"}, {"44805.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"}, {"44562.189571759256", "[$-406]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44593.189571759256", "[$-406]mmmm dd yyyy h:mm AM/PM", "februar 01 2022 4:32 AM"}, {"44621.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "marts 01 2022 4:32 AM"}, {"44652.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"}, {"44682.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"}, {"44713.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"}, {"44774.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "august 01 2022 4:32 AM"}, {"44805.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"}, {"44835.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"}, {"44866.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"}, {"44896.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"}, {"44562.189571759256", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44593.189571759256", "[$-406]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44743.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44774.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM lø"}, {"44866.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM ti"}, {"44896.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM torsdag"}, {"44562.189571759256", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062C\u062F\u06CC 01 2022 4:32 \u0642.\u0638"}, {"44593.189571759256", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062F\u0644\u0648 01 2022 4:32 \u0642.\u0638"}, {"44621.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062D\u0648\u062A 01 2022 4:32 \u0642.\u0638"}, {"44652.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062D\u0645\u0644 01 2022 4:32 \u0642.\u0638"}, {"44682.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062B\u0648\u0631 01 2022 4:32 \u0642.\u0638"}, {"44713.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0632\u0627 01 2022 4:32 \u0642.\u0638"}, {"44743.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0633\u0631\u0637\u0627\u0646 01 2022 4:32 \u0642.\u0638"}, {"44774.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0627\u0633\u062F 01 2022 4:32 \u0642.\u0638"}, {"44805.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0633\u0646\u0628\u0644\u0647 01 2022 4:32 \u0642.\u0638"}, {"44835.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0645\u06CC\u0632\u0627\u0646 01 2022 4:32 \u0642.\u0638"}, {"44866.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0639\u0642\u0631\u0628 01 2022 4:32 \u0642.\u0638"}, {"44896.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0642\u0648\u0633 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44593.189571759256", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0641\u0648\u0631\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44621.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 01 2022 4:32 \u0642.\u0638"}, {"44652.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0622\u0648\u0631\u064A\u0644 01 2022 4:32 \u0642.\u0638"}, {"44682.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0647 01 2022 4:32 \u0642.\u0638"}, {"44713.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0648\u0626\u0646 01 2022 4:32 \u0642.\u0638"}, {"44743.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0648\u0626\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44774.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0627\u0648\u062A 01 2022 4:32 \u0642.\u0638"}, {"44805.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0633\u067E\u062A\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"}, {"44835.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0627\u064F\u0643\u062A\u0628\u0631 01 2022 4:32 \u0642.\u0638"}, {"44866.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0646\u0648\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"}, {"44896.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u062F\u0633\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"}, {"44593.189571759256", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0641 01 2022 4:32 \u0642.\u0638"}, {"44621.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638"}, {"44652.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0622 01 2022 4:32 \u0642.\u0638"}, {"44682.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638"}, {"44713.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"}, {"44743.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"}, {"44774.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638"}, {"44805.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0633 01 2022 4:32 \u0642.\u0638"}, {"44835.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM aaa", "\u0627 01 2022 4:32 \u0642.\u0638 \u0634\u0646\u0628\u0647"}, {"44866.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0646 01 2022 4:32 \u0642.\u0638 \u0633\u0647\u200C \u0634\u0646\u0628\u0647"}, {"44896.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM dddd", "\u062F 01 2022 4:32 \u0642.\u0638 \u067E\u0646\u062C\u0634\u0646\u0628\u0647"}, {"44562.189571759256", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062C\u062F\u06CC 01 2022 4:32 \u0642.\u0638."}, {"44593.189571759256", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062F\u0644\u0648 01 2022 4:32 \u0642.\u0638."}, {"44621.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062D\u0648\u062A 01 2022 4:32 \u0642.\u0638."}, {"44652.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062D\u0645\u0644 01 2022 4:32 \u0642.\u0638."}, {"44682.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062B\u0648\u0631 01 2022 4:32 \u0642.\u0638."}, {"44713.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0632\u0627 01 2022 4:32 \u0642.\u0638."}, {"44743.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0633\u0631\u0637\u0627\u0646 01 2022 4:32 \u0642.\u0638."}, {"44774.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0627\u0633\u062F 01 2022 4:32 \u0642.\u0638."}, {"44805.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0633\u0646\u0628\u0644\u0647 01 2022 4:32 \u0642.\u0638."}, {"44835.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0645\u06CC\u0632\u0627\u0646 01 2022 4:32 \u0642.\u0638."}, {"44866.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0639\u0642\u0631\u0628 01 2022 4:32 \u0642.\u0638."}, {"44896.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0642\u0648\u0633 01 2022 4:32 \u0642.\u0638."}, {"44562.189571759256", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u0642.\u0638."}, {"44593.189571759256", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0641\u0628\u0631\u0648\u0631\u06CC 01 2022 4:32 \u0642.\u0638."}, {"44621.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 01 2022 4:32 \u0642.\u0638."}, {"44652.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u067E\u0631\u06CC\u0644 01 2022 4:32 \u0642.\u0638."}, {"44682.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0645\u06CC 01 2022 4:32 \u0642.\u0638."}, {"44713.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0646 01 2022 4:32 \u0642.\u0638."}, {"44743.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0644\u0627\u06CC 01 2022 4:32 \u0642.\u0638."}, {"44774.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u06AF\u0633\u062A 01 2022 4:32 \u0642.\u0638."}, {"44805.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0633\u067E\u062A\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."}, {"44835.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u06A9\u062A\u0648\u0628\u0631 01 2022 4:32 \u0642.\u0638."}, {"44866.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0646\u0648\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."}, {"44896.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062F\u0633\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."}, {"44562.189571759256", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."}, {"44593.189571759256", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0641 01 2022 4:32 \u0642.\u0638."}, {"44621.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638."}, {"44652.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638."}, {"44682.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638."}, {"44713.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."}, {"44743.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."}, {"44774.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638."}, {"44805.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0633 01 2022 4:32 \u0642.\u0638."}, {"44835.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM aaa", "\u0627 01 2022 4:32 \u0642.\u0638. \u0634\u0646\u0628\u0647"}, {"44866.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0646 01 2022 4:32 \u0642.\u0638. \u0633\u0647\u200C \u0634\u0646\u0628\u0647"}, {"44896.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM dddd", "\u062F 01 2022 4:32 \u0642.\u0638. \u067E\u0646\u062C\u0634\u0646\u0628\u0647"}, {"44562.189571759256", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-65]mmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44866.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44896.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44562.189571759256", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44866.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44896.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44562.189571759256", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u078A 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0790 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM aaa", "\u0787 01 2022 4:32 \u0789\u0786 \u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA"}, {"44866.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM ddd", "\u0782 01 2022 4:32 \u0789\u0786 \u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6"}, {"44896.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM dddd", "\u0791 01 2022 4:32 \u0789\u0786 \u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8"}, {"44562.189571759256", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-465]mmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44866.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44896.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44562.189571759256", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44866.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44896.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"}, {"44562.189571759256", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44593.189571759256", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u078A 01 2022 4:32 \u0789\u0786"}, {"44621.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"}, {"44652.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"}, {"44682.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"}, {"44713.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44743.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"}, {"44774.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"}, {"44805.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0790 01 2022 4:32 \u0789\u0786"}, {"44835.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM aaa", "\u0787 01 2022 4:32 \u0789\u0786 \u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA"}, {"44866.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM ddd", "\u0782 01 2022 4:32 \u0789\u0786 \u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6"}, {"44896.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM dddd", "\u0791 01 2022 4:32 \u0789\u0786 \u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8"}, {"44562.189571759256", "[$-13]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-13]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"}, {"44652.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"}, {"44682.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"}, {"44805.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"}, {"44562.189571759256", "[$-13]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-13]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"}, {"44652.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"}, {"44682.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"}, {"44774.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"}, {"44805.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"}, {"44835.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"}, {"44866.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"}, {"44896.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"}, {"44562.189571759256", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44593.189571759256", "[$-13]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44743.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44774.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"}, {"44866.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"}, {"44896.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"}, {"44562.189571759256", "[$-813]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-813]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"}, {"44652.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"}, {"44682.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"}, {"44805.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"}, {"44562.189571759256", "[$-813]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-813]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"}, {"44652.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"}, {"44682.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"}, {"44774.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"}, {"44805.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"}, {"44835.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"}, {"44866.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"}, {"44896.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"}, {"44562.189571759256", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44593.189571759256", "[$-813]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44743.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44774.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"}, {"44866.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"}, {"44896.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"}, {"44621.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"}, {"44652.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"}, {"44682.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"}, {"44805.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"}, {"44562.189571759256", "[$-413]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-413]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"}, {"44652.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"}, {"44682.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"}, {"44713.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"}, {"44774.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"}, {"44805.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"}, {"44835.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"}, {"44866.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"}, {"44896.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"}, {"44562.189571759256", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44593.189571759256", "[$-413]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"}, {"44621.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44652.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44682.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"}, {"44713.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44743.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44774.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"}, {"44805.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44835.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"}, {"44866.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"}, {"44896.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"}, {"44562.189571759256", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44593.189571759256", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44621.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44652.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44682.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44713.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44743.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44774.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44805.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44835.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44866.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44896.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44562.189571759256", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F44\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44593.189571759256", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44621.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44652.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44682.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44713.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44743.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44774.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44805.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44835.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44866.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44896.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44562.189571759256", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44593.189571759256", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44621.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44652.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44682.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44713.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44743.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44774.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44805.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"}, {"44835.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F49\u0F72\u0F0B"}, {"44866.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F63\u0FB7\u0F42\u0F0B"}, {"44896.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0B"}, {"43543.503206018519", "[$-9]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-9]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-9]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-1000]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1000]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1000]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"}, {"43543.503206018519", "[$-c09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-c09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-c09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"}, {"43543.503206018519", "[$-2809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-1009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-2409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-3C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-4009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-1809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-1809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-1809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"}, {"43543.503206018519", "[$-2009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-4409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-1409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-3409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-4809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-1C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-1C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-2C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-2C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-4C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-4C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"}, {"43543.503206018519", "[$-809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"}, {"43543.503206018519", "[$-3009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-3009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"}, {"43543.503206018519", "[$-25]mmm dd yyyy h:mm AM/PM aaa", "märts 19 2019 12:04 PM T"}, {"43543.503206018519", "[$-25]mmmm dd yyyy h:mm AM/PM ddd", "märts 19 2019 12:04 PM T"}, {"43543.503206018519", "[$-25]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM teisipäev"}, {"43543.503206018519", "[$-425]mmm dd yyyy h:mm AM/PM aaa", "märts 19 2019 12:04 PM T"}, {"43543.503206018519", "[$-425]mmmm dd yyyy h:mm AM/PM ddd", "märts 19 2019 12:04 PM T"}, {"43543.503206018519", "[$-425]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM teisipäev"}, {"43543.503206018519", "[$-38]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 um sein. týs."}, {"43543.503206018519", "[$-38]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 um sein. týs."}, {"43543.503206018519", "[$-38]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 um sein. týsdagur"}, {"43543.503206018519", "[$-438]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 um sein. týs."}, {"43543.503206018519", "[$-438]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 um sein. týs."}, {"43543.503206018519", "[$-438]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 um sein. týsdagur"}, {"43543.503206018519", "[$-64]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-64]mmmm dd yyyy h:mm AM/PM ddd", "Marso 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-64]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 PM Martes"}, {"43543.503206018519", "[$-464]mmm dd yyyy h:mm AM/PM ddd", "Mar 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-464]mmmm dd yyyy h:mm AM/PM ddd", "Marso 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-464]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 PM Martes"}, {"43543.503206018519", "[$-B]mmm dd yyyy h:mm AM/PM aaa", "maalis 19 2019 12:04 ip. ti"}, {"43543.503206018519", "[$-B]mmmm dd yyyy h:mm AM/PM ddd", "maaliskuu 19 2019 12:04 ip. ti"}, {"43543.503206018519", "[$-B]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 ip. tiistai"}, {"43543.503206018519", "[$-40B]mmm dd yyyy h:mm AM/PM aaa", "maalis 19 2019 12:04 ip. ti"}, {"43543.503206018519", "[$-40B]mmmm dd yyyy h:mm AM/PM ddd", "maaliskuu 19 2019 12:04 ip. ti"}, {"43543.503206018519", "[$-40B]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 ip. tiistai"}, {"44562.189571759256", "[$-C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-80C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-80C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-80C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-80C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-80C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-80C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-2c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 mat."}, {"44562.189571759256", "[$-2c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 mat."}, {"44562.189571759256", "[$-2c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 mat."}, {"43543.503206018519", "[$-2c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 soir mar."}, {"43543.503206018519", "[$-2c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 soir mar."}, {"43543.503206018519", "[$-2c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 soir mardi"}, {"44562.189571759256", "[$-c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-1C0C]mmm dd yyyy h:mm AM/PM", "Janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C0C]mmmm dd yyyy h:mm AM/PM", "Janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C0C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"43543.503206018519", "[$-1C0C]mmm dd yyyy h:mm AM/PM aaa", "Mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-1C0C]mmmm dd yyyy h:mm AM/PM ddd", "Mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-1C0C]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-240C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-240C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-240C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-240C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-240C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-240C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-300C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-300C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-300C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-300C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-300C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-300C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-40C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-40C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-40C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-40C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-40C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-40C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-3c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-3c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-3c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-3c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-140C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-140C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-140C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-140C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-140C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-140C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-340C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-340C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-340C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-340C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-340C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-340C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-380C]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-380C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-380C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-380C]mmm dd yyyy h:mm AM/PM aaa", "mar. 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-380C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-380C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-180C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-180C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-180C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-180C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-180C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-180C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-200C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-200C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-200C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-200C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-200C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-200C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-280C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-280C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-280C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-280C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-280C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-280C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-100C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-100C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"}, {"44562.189571759256", "[$-100C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"43543.503206018519", "[$-100C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-100C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."}, {"43543.503206018519", "[$-100C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-62]m dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"}, {"44562.189571759256", "[$-62]mm dd yyyy h:mm AM/PM", "01 01 2022 4:32 AM"}, {"44562.189571759256", "[$-62]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-62]mmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"}, {"44562.189571759256", "[$-62]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-62]mmmmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"}, {"43543.503206018519", "[$-62]m dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"}, {"43543.503206018519", "[$-62]mm dd yyyy h:mm AM/PM", "03 19 2019 12:04 PM"}, {"43543.503206018519", "[$-62]mmm dd yyyy h:mm AM/PM", "Mrt 19 2019 12:04 PM"}, {"43543.503206018519", "[$-62]mmmm dd yyyy h:mm AM/PM aaa", "Maart 19 2019 12:04 PM tii"}, {"43543.503206018519", "[$-62]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM tii"}, {"43543.503206018519", "[$-62]mmmmmm dd yyyy h:mm AM/PM dddd", "Maart 19 2019 12:04 PM tiisdei"}, {"44562.189571759256", "[$-462]m dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"}, {"44562.189571759256", "[$-462]mm dd yyyy h:mm AM/PM", "01 01 2022 4:32 AM"}, {"44562.189571759256", "[$-462]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-462]mmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"}, {"44562.189571759256", "[$-462]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-462]mmmmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"}, {"43543.503206018519", "[$-462]m dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"}, {"43543.503206018519", "[$-462]mm dd yyyy h:mm AM/PM", "03 19 2019 12:04 PM"}, {"43543.503206018519", "[$-462]mmm dd yyyy h:mm AM/PM", "Mrt 19 2019 12:04 PM"}, {"43543.503206018519", "[$-462]mmmm dd yyyy h:mm AM/PM aaa", "Maart 19 2019 12:04 PM tii"}, {"43543.503206018519", "[$-462]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM tii"}, {"43543.503206018519", "[$-462]mmmmmm dd yyyy h:mm AM/PM dddd", "Maart 19 2019 12:04 PM tiisdei"}, {"44562.189571759256", "[$-67]mmm dd yyyy h:mm AM/PM", "sii 01 2022 4:32 AM"}, {"44562.189571759256", "[$-67]mmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"}, {"44562.189571759256", "[$-67]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-67]mmmmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"}, {"43543.503206018519", "[$-67]mmm dd yyyy h:mm AM/PM", "mbo 19 2019 12:04 PM"}, {"43543.503206018519", "[$-67]mmmm dd yyyy h:mm AM/PM aaa", "mbooy 19 2019 12:04 PM maw"}, {"43543.503206018519", "[$-67]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM maw"}, {"43543.503206018519", "[$-67]mmmmmm dd yyyy h:mm AM/PM dddd", "mbooy 19 2019 12:04 PM mawbaare"}, {"44562.189571759256", "[$-7C67]mmm dd yyyy h:mm AM/PM", "sii 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C67]mmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C67]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C67]mmmmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C67]mmm dd yyyy h:mm AM/PM", "mbo 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C67]mmmm dd yyyy h:mm AM/PM aaa", "mbooy 19 2019 12:04 PM maw"}, {"43543.503206018519", "[$-7C67]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM maw"}, {"43543.503206018519", "[$-7C67]mmmmmm dd yyyy h:mm AM/PM dddd", "mbooy 19 2019 12:04 PM mawbaare"}, {"44562.189571759256", "[$-467]mmm dd yyyy h:mm AM/PM", "samw 01 2022 4:32 subaka"}, {"44562.189571759256", "[$-467]mmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 subaka"}, {"44562.189571759256", "[$-467]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 subaka"}, {"44562.189571759256", "[$-467]mmmmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 subaka"}, {"43543.503206018519", "[$-467]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 kikiiɗe"}, {"43543.503206018519", "[$-467]mmmm dd yyyy h:mm AM/PM aaa", "marsa 19 2019 12:04 kikiiɗe tal."}, {"43543.503206018519", "[$-467]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 kikiiɗe tal."}, {"43543.503206018519", "[$-467]mmmmmm dd yyyy h:mm AM/PM dddd", "marsa 19 2019 12:04 kikiiɗe talaata"}, {"44562.189571759256", "[$-867]mmm dd yyyy h:mm AM/PM", "samw 01 2022 4:32 AM"}, {"44562.189571759256", "[$-867]mmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 AM"}, {"44562.189571759256", "[$-867]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-867]mmmmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 AM"}, {"43543.503206018519", "[$-867]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-867]mmmm dd yyyy h:mm AM/PM aaa", "marsa 19 2019 12:04 PM tal."}, {"43543.503206018519", "[$-867]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tal."}, {"43543.503206018519", "[$-867]mmmmmm dd yyyy h:mm AM/PM dddd", "marsa 19 2019 12:04 PM talaata"}, {"44562.189571759256", "[$-56]mmm dd yyyy h:mm AM/PM", "Xan. 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-56]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-56]mmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-56]mmmmm dd yyyy h:mm AM/PM", "M 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."}, {"44562.189571759256", "[$-56]mmm dd yyyy h:mm AM/PM", "Xan. 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-56]mmm dd yyyy h:mm AM/PM aaa", "Mar. 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-56]mmmm dd yyyy h:mm AM/PM ddd", "Marzo 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-56]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 p.m. martes"}, {"43543.503206018519", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-456]mmm dd yyyy h:mm AM/PM aaa", "Mar. 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-456]mmmm dd yyyy h:mm AM/PM ddd", "Marzo 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-456]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 p.m. martes"}, {"44562.189571759256", "[$-37]mmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-37]mmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-37]mmmmm dd yyyy h:mm AM/PM", "\u10D8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-37]mmmmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"}, {"43543.503206018519", "[$-37]mmm dd yyyy h:mm AM/PM", "\u10DB\u10D0\u10E0 19 2019 12:04 PM"}, {"43543.503206018519", "[$-37]mmmm dd yyyy h:mm AM/PM aaa", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."}, {"43543.503206018519", "[$-37]mmmmm dd yyyy h:mm AM/PM ddd", "\u10DB 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."}, {"43543.503206018519", "[$-37]mmmmmm dd yyyy h:mm AM/PM dddd", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8"}, {"44562.189571759256", "[$-437]mmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-437]mmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-437]mmmmm dd yyyy h:mm AM/PM", "\u10D8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-437]mmmmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"}, {"43543.503206018519", "[$-437]mmm dd yyyy h:mm AM/PM", "\u10DB\u10D0\u10E0 19 2019 12:04 PM"}, {"43543.503206018519", "[$-437]mmmm dd yyyy h:mm AM/PM aaa", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."}, {"43543.503206018519", "[$-437]mmmmm dd yyyy h:mm AM/PM ddd", "\u10DB 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."}, {"43543.503206018519", "[$-437]mmmmmm dd yyyy h:mm AM/PM dddd", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8"}, {"43543.503206018519", "[$-7]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-7]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-7]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"}, {"44562.189571759256", "[$-C07]mmm dd yyyy h:mm AM/PM aaa", "Jän 01 2022 4:32 AM Sa"}, {"44562.189571759256", "[$-C07]mmmm dd yyyy h:mm AM/PM ddd", "Jänner 01 2022 4:32 AM Sa"}, {"44562.189571759256", "[$-C07]mmmmm dd yyyy h:mm AM/PM dddd", "J 01 2022 4:32 AM Samstag"}, {"43543.503206018519", "[$-407]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-407]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-407]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"}, {"43543.503206018519", "[$-1407]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-1407]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-1407]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"}, {"43543.503206018519", "[$-1007]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-1007]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-1007]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"}, {"43543.503206018519", "[$-807]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-807]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"}, {"43543.503206018519", "[$-807]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"}, {"44562.189571759256", "[$-8]mmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-8]mmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-8]mmmmm dd yyyy h:mm AM/PM", "\u0399 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-8]mmmmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"}, {"43543.503206018519", "[$-8]mmm dd yyyy h:mm AM/PM", "\u039C\u03B1\u03C1 19 2019 12:04 \u03BC\u03BC"}, {"43543.503206018519", "[$-8]mmmm dd yyyy h:mm AM/PM aaa", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"}, {"43543.503206018519", "[$-8]mmmmm dd yyyy h:mm AM/PM ddd", "\u039C 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"}, {"43543.503206018519", "[$-8]mmmmmm dd yyyy h:mm AM/PM dddd", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03AF\u03C4\u03B7"}, {"44562.189571759256", "[$-408]mmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-408]mmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-408]mmmmm dd yyyy h:mm AM/PM", "\u0399 01 2022 4:32 \u03C0\u03BC"}, {"44562.189571759256", "[$-408]mmmmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"}, {"43543.503206018519", "[$-408]mmm dd yyyy h:mm AM/PM", "\u039C\u03B1\u03C1 19 2019 12:04 \u03BC\u03BC"}, {"43543.503206018519", "[$-408]mmmm dd yyyy h:mm AM/PM aaa", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"}, {"43543.503206018519", "[$-408]mmmmm dd yyyy h:mm AM/PM ddd", "\u039C 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"}, {"43543.503206018519", "[$-408]mmmmmm dd yyyy h:mm AM/PM dddd", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03AF\u03C4\u03B7"}, {"44562.189571759256", "[$-6F]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6F]mmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6F]mmmmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-6F]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-6F]mmmm dd yyyy h:mm AM/PM aaa", "marsi 19 2019 12:04 PM marl."}, {"43543.503206018519", "[$-6F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM marl."}, {"43543.503206018519", "[$-6F]mmmmmm dd yyyy h:mm AM/PM dddd", "marsi 19 2019 12:04 PM marlunngorneq"}, {"44562.189571759256", "[$-46F]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46F]mmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46F]mmmmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-46F]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-46F]mmmm dd yyyy h:mm AM/PM aaa", "marsi 19 2019 12:04 PM marl."}, {"43543.503206018519", "[$-46F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM marl."}, {"43543.503206018519", "[$-46F]mmmmmm dd yyyy h:mm AM/PM dddd", "marsi 19 2019 12:04 PM marlunngorneq"}, {"44562.189571759256", "[$-74]mmm dd yyyy h:mm AM/PM", "jteĩ 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-74]mmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-74]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-74]mmmmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-74]mmm dd yyyy h:mm AM/PM", "japy 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-74]mmmm dd yyyy h:mm AM/PM aaa", "jasyapy 19 2019 12:04 p.m. apy"}, {"43543.503206018519", "[$-74]mmmmm dd yyyy h:mm AM/PM ddd", "j 19 2019 12:04 p.m. apy"}, {"43543.503206018519", "[$-74]mmmmmm dd yyyy h:mm AM/PM dddd", "jasyapy 19 2019 12:04 p.m. araapy"}, {"44562.189571759256", "[$-474]mmm dd yyyy h:mm AM/PM", "jteĩ 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-474]mmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-474]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-474]mmmmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-474]mmm dd yyyy h:mm AM/PM", "japy 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-474]mmmm dd yyyy h:mm AM/PM aaa", "jasyapy 19 2019 12:04 p.m. apy"}, {"43543.503206018519", "[$-474]mmmmm dd yyyy h:mm AM/PM ddd", "j 19 2019 12:04 p.m. apy"}, {"43543.503206018519", "[$-474]mmmmmm dd yyyy h:mm AM/PM dddd", "jasyapy 19 2019 12:04 p.m. araapy"}, {"44562.189571759256", "[$-47]mmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-47]mmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-47]mmmmm dd yyyy h:mm AM/PM", "\u0A9C 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-47]mmmmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"43543.503206018519", "[$-47]mmm dd yyyy h:mm AM/PM", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"43543.503206018519", "[$-47]mmmm dd yyyy h:mm AM/PM aaa", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"}, {"43543.503206018519", "[$-47]mmmmm dd yyyy h:mm AM/PM ddd", "\u0AAE 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"}, {"43543.503206018519", "[$-47]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0"}, {"44562.189571759256", "[$-447]mmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-447]mmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-447]mmmmm dd yyyy h:mm AM/PM", "\u0A9C 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"44562.189571759256", "[$-447]mmmmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"43543.503206018519", "[$-447]mmm dd yyyy h:mm AM/PM", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"}, {"43543.503206018519", "[$-447]mmmm dd yyyy h:mm AM/PM aaa", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"}, {"43543.503206018519", "[$-447]mmmmm dd yyyy h:mm AM/PM ddd", "\u0AAE 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"}, {"43543.503206018519", "[$-447]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0"}, {"44562.189571759256", "[$-68]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-68]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-68]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-68]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-68]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-68]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-68]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-68]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"}, {"44562.189571759256", "[$-7C68]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C68]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C68]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C68]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C68]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C68]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-7C68]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-7C68]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"}, {"44562.189571759256", "[$-468]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-468]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-468]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-468]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-468]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-468]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-468]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"}, {"43543.503206018519", "[$-468]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"}, {"44562.189571759256", "[$-75]mmm dd yyyy h:mm AM/PM", "Ian. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-75]mmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"}, {"44562.189571759256", "[$-75]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"}, {"44562.189571759256", "[$-75]mmmmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"}, {"43543.503206018519", "[$-75]mmm dd yyyy h:mm AM/PM", "Mal. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-75]mmmm dd yyyy h:mm AM/PM aaa", "Malaki 19 2019 12:04 PM P2"}, {"43543.503206018519", "[$-75]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM P2"}, {"43543.503206018519", "[$-75]mmmmmm dd yyyy h:mm AM/PM dddd", "Malaki 19 2019 12:04 PM Po\u02BBalua"}, {"44562.189571759256", "[$-475]mmm dd yyyy h:mm AM/PM", "Ian. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-475]mmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"}, {"44562.189571759256", "[$-475]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"}, {"44562.189571759256", "[$-475]mmmmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"}, {"43543.503206018519", "[$-475]mmm dd yyyy h:mm AM/PM", "Mal. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-475]mmmm dd yyyy h:mm AM/PM aaa", "Malaki 19 2019 12:04 PM P2"}, {"43543.503206018519", "[$-475]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM P2"}, {"43543.503206018519", "[$-475]mmmmmm dd yyyy h:mm AM/PM dddd", "Malaki 19 2019 12:04 PM Po\u02BBalua"}, {"44562.189571759256", "[$-D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5 01 2022 4:32 AM"}, {"44562.189571759256", "[$-D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 AM"}, {"44562.189571759256", "[$-D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"}, {"43543.503206018519", "[$-D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E8\u05E5 19 2019 12:04 PM"}, {"43543.503206018519", "[$-D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9"}, {"44562.189571759256", "[$-40D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5 01 2022 4:32 AM"}, {"44562.189571759256", "[$-40D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"}, {"44562.189571759256", "[$-40D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 AM"}, {"44562.189571759256", "[$-40D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"}, {"43543.503206018519", "[$-40D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E8\u05E5 19 2019 12:04 PM"}, {"43543.503206018519", "[$-40D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-40D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-40D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9"}, {"44562.189571759256", "[$-39]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-39]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-39]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-39]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-39]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-39]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."}, {"43543.503206018519", "[$-39]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."}, {"43543.503206018519", "[$-39]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932\u0935\u093E\u0930"}, {"44562.189571759256", "[$-439]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-439]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-439]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-439]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-439]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-439]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."}, {"43543.503206018519", "[$-439]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."}, {"43543.503206018519", "[$-439]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932\u0935\u093E\u0930"}, {"44562.189571759256", "[$-E]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 de."}, {"44562.189571759256", "[$-E]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."}, {"44562.189571759256", "[$-E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 de."}, {"44562.189571759256", "[$-E]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."}, {"43543.503206018519", "[$-E]mmm dd yyyy h:mm AM/PM", "márc. 19 2019 12:04 du."}, {"43543.503206018519", "[$-E]mmmm dd yyyy h:mm AM/PM aaa", "március 19 2019 12:04 du. K"}, {"43543.503206018519", "[$-E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 du. K"}, {"43543.503206018519", "[$-E]mmmmmm dd yyyy h:mm AM/PM dddd", "március 19 2019 12:04 du. kedd"}, {"44562.189571759256", "[$-40E]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 de."}, {"44562.189571759256", "[$-40E]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."}, {"44562.189571759256", "[$-40E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 de."}, {"44562.189571759256", "[$-40E]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."}, {"43543.503206018519", "[$-40E]mmm dd yyyy h:mm AM/PM", "márc. 19 2019 12:04 du."}, {"43543.503206018519", "[$-40E]mmmm dd yyyy h:mm AM/PM aaa", "március 19 2019 12:04 du. K"}, {"43543.503206018519", "[$-40E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 du. K"}, {"43543.503206018519", "[$-40E]mmmmmm dd yyyy h:mm AM/PM dddd", "március 19 2019 12:04 du. kedd"}, {"44562.189571759256", "[$-F]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-F]mmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-F]mmmmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."}, {"43543.503206018519", "[$-F]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 e.h."}, {"43543.503206018519", "[$-F]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.h. þri."}, {"43543.503206018519", "[$-F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.h. þri."}, {"43543.503206018519", "[$-F]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.h. þriðjudagur"}, {"44562.189571759256", "[$-40F]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-40F]mmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-40F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.h."}, {"44562.189571759256", "[$-40F]mmmmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."}, {"43543.503206018519", "[$-40F]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 e.h."}, {"43543.503206018519", "[$-40F]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.h. þri."}, {"43543.503206018519", "[$-40F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.h. þri."}, {"43543.503206018519", "[$-40F]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.h. þriðjudagur"}, {"44562.189571759256", "[$-70]mmm dd yyyy h:mm AM/PM", "Jen 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-70]mmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-70]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-70]mmmmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."}, {"43543.503206018519", "[$-70]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 P.M."}, {"43543.503206018519", "[$-70]mmmm dd yyyy h:mm AM/PM aaa", "Machị 19 2019 12:04 P.M. Tiu"}, {"43543.503206018519", "[$-70]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 P.M. Tiu"}, {"43543.503206018519", "[$-70]mmmmmm dd yyyy h:mm AM/PM dddd", "Machị 19 2019 12:04 P.M. Tiuzdee"}, {"44562.189571759256", "[$-470]mmm dd yyyy h:mm AM/PM", "Jen 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-470]mmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-470]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 A.M."}, {"44562.189571759256", "[$-470]mmmmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."}, {"43543.503206018519", "[$-470]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 P.M."}, {"43543.503206018519", "[$-470]mmmm dd yyyy h:mm AM/PM aaa", "Machị 19 2019 12:04 P.M. Tiu"}, {"43543.503206018519", "[$-470]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 P.M. Tiu"}, {"43543.503206018519", "[$-470]mmmmmm dd yyyy h:mm AM/PM dddd", "Machị 19 2019 12:04 P.M. Tiuzdee"}, {"44562.189571759256", "[$-21]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-21]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-21]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-21]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-21]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-21]mmmm dd yyyy h:mm AM/PM aaa", "Maret 19 2019 12:04 PM Sel"}, {"43543.503206018519", "[$-21]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sel"}, {"43543.503206018519", "[$-21]mmmmmm dd yyyy h:mm AM/PM dddd", "Maret 19 2019 12:04 PM Selasa"}, {"44562.189571759256", "[$-421]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-421]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-421]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-421]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-421]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-421]mmmm dd yyyy h:mm AM/PM aaa", "Maret 19 2019 12:04 PM Sel"}, {"43543.503206018519", "[$-421]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sel"}, {"43543.503206018519", "[$-421]mmmmmm dd yyyy h:mm AM/PM dddd", "Maret 19 2019 12:04 PM Selasa"}, {"44562.189571759256", "[$-5D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-5D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"}, {"43543.503206018519", "[$-5D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-5D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-5D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"}, {"44562.189571759256", "[$-7C5D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C5D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C5D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-7C5D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-7C5D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"}, {"44562.189571759256", "[$-85D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-85D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"}, {"43543.503206018519", "[$-85D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-85D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"}, {"43543.503206018519", "[$-85D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"}, {"44562.189571759256", "[$-785D]mmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4 01 2022 4:32 AM"}, {"44562.189571759256", "[$-785D]mmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"}, {"44562.189571759256", "[$-785D]mmmmm dd yyyy h:mm AM/PM", "\u152E 01 2022 4:32 AM"}, {"44562.189571759256", "[$-785D]mmmmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"}, {"43543.503206018519", "[$-785D]mmm dd yyyy h:mm AM/PM", "\u14AB\u1466\u14EF 19 2019 12:04 PM"}, {"43543.503206018519", "[$-785D]mmmm dd yyyy h:mm AM/PM aaa", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431"}, {"43543.503206018519", "[$-785D]mmmmm dd yyyy h:mm AM/PM ddd", "\u14AB 19 2019 12:04 PM \u140A\u1403\u1449\u1431"}, {"43543.503206018519", "[$-785D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431\u1585"}, {"44562.189571759256", "[$-45D]mmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45D]mmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45D]mmmmm dd yyyy h:mm AM/PM", "\u152E 01 2022 4:32 AM"}, {"44562.189571759256", "[$-45D]mmmmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"}, {"43543.503206018519", "[$-45D]mmm dd yyyy h:mm AM/PM", "\u14AB\u1466\u14EF 19 2019 12:04 PM"}, {"43543.503206018519", "[$-45D]mmmm dd yyyy h:mm AM/PM aaa", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431"}, {"43543.503206018519", "[$-45D]mmmmm dd yyyy h:mm AM/PM ddd", "\u14AB 19 2019 12:04 PM \u140A\u1403\u1449\u1431"}, {"43543.503206018519", "[$-45D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431\u1585"}, {"44562.189571759256", "[$-3C]mmm dd yyyy h:mm AM/PM", "Ean 01 2022 4:32 r.n."}, {"44593.189571759256", "[$-3C]mmm dd yyyy h:mm AM/PM", "Feabh 01 2022 4:32 r.n."}, {"44621.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."}, {"44652.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Aib 01 2022 4:32 r.n."}, {"44682.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Beal 01 2022 4:32 r.n."}, {"44713.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Meith 01 2022 4:32 r.n."}, {"44743.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."}, {"44774.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Lún 01 2022 4:32 r.n."}, {"44805.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "MFómh 01 2022 4:32 r.n."}, {"44835.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "DFómh 01 2022 4:32 r.n."}, {"44866.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Samh 01 2022 4:32 r.n."}, {"44896.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Noll 01 2022 4:32 r.n."}, {"44562.189571759256", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Eanáir 01 2022 4:32 r.n."}, {"44593.189571759256", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Feabhra 01 2022 4:32 r.n."}, {"44621.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."}, {"44652.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Aibreán 01 2022 4:32 r.n."}, {"44682.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Bealtaine 01 2022 4:32 r.n."}, {"44713.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Meitheamh 01 2022 4:32 r.n."}, {"44743.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."}, {"44774.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Lúnasa 01 2022 4:32 r.n."}, {"44805.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Meán Fómhair 01 2022 4:32 r.n."}, {"44835.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Deireadh Fómhair 01 2022 4:32 r.n."}, {"44866.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Samhain 01 2022 4:32 r.n."}, {"44896.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Nollaig 01 2022 4:32 r.n."}, {"44562.189571759256", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 r.n."}, {"44593.189571759256", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 r.n."}, {"44621.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."}, {"44652.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 r.n."}, {"44682.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "B 01 2022 4:32 r.n."}, {"44713.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."}, {"44743.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 r.n."}, {"44774.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "L 01 2022 4:32 r.n."}, {"44805.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."}, {"44835.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM aaa", "D 01 2022 4:32 r.n. Sath"}, {"44866.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM ddd", "S 01 2022 4:32 r.n. Máirt"}, {"44896.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM dddd", "N 01 2022 4:32 r.n. Déardaoin"}, {"44562.189571759256", "[$-83C]mmm dd yyyy h:mm AM/PM", "Ean 01 2022 4:32 r.n."}, {"44593.189571759256", "[$-83C]mmm dd yyyy h:mm AM/PM", "Feabh 01 2022 4:32 r.n."}, {"44621.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."}, {"44652.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Aib 01 2022 4:32 r.n."}, {"44682.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Beal 01 2022 4:32 r.n."}, {"44713.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Meith 01 2022 4:32 r.n."}, {"44743.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."}, {"44774.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Lún 01 2022 4:32 r.n."}, {"44805.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "MFómh 01 2022 4:32 r.n."}, {"44835.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "DFómh 01 2022 4:32 r.n."}, {"44866.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Samh 01 2022 4:32 r.n."}, {"44896.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Noll 01 2022 4:32 r.n."}, {"44562.189571759256", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Eanáir 01 2022 4:32 r.n."}, {"44593.189571759256", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Feabhra 01 2022 4:32 r.n."}, {"44621.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."}, {"44652.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Aibreán 01 2022 4:32 r.n."}, {"44682.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Bealtaine 01 2022 4:32 r.n."}, {"44713.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Meitheamh 01 2022 4:32 r.n."}, {"44743.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."}, {"44774.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Lúnasa 01 2022 4:32 r.n."}, {"44805.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Meán Fómhair 01 2022 4:32 r.n."}, {"44835.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM aaa", "Deireadh Fómhair 01 2022 4:32 r.n. Sath"}, {"44866.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM ddd", "Samhain 01 2022 4:32 r.n. Máirt"}, {"44896.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM dddd", "Nollaig 01 2022 4:32 r.n. Déardaoin"}, {"43543.503206018519", "[$-10]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-10]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-10]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"}, {"43543.503206018519", "[$-410]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-410]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-410]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"}, {"43543.503206018519", "[$-810]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-810]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-810]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"}, {"43543.503206018519", "[$-11]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 午後 火"}, {"43543.503206018519", "[$-11]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 午後 火"}, {"43543.503206018519", "[$-11]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 午後 火曜日"}, {"43543.503206018519", "[$-411]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 午後 火"}, {"43543.503206018519", "[$-411]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 午後 火"}, {"43543.503206018519", "[$-411]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 午後 火曜日"}, {"44562.189571759256", "[$-4B]mmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-4B]mmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-4B]mmmmm dd yyyy h:mm AM/PM", "\u0C9C 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-4B]mmmmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"43543.503206018519", "[$-4B]mmm dd yyyy h:mm AM/PM", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8"}, {"43543.503206018519", "[$-4B]mmmm dd yyyy h:mm AM/PM aaa", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."}, {"43543.503206018519", "[$-4B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0CAE 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."}, {"43543.503206018519", "[$-4B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0"}, {"44562.189571759256", "[$-44B]mmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-44B]mmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-44B]mmmmm dd yyyy h:mm AM/PM", "\u0C9C 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"44562.189571759256", "[$-44B]mmmmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"}, {"43543.503206018519", "[$-44B]mmm dd yyyy h:mm AM/PM", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8"}, {"43543.503206018519", "[$-44B]mmmm dd yyyy h:mm AM/PM aaa", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."}, {"43543.503206018519", "[$-44B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0CAE 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."}, {"43543.503206018519", "[$-44B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0"}, {"44562.189571759256", "[$-471]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-471]mmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"}, {"44562.189571759256", "[$-471]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-471]mmmmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"}, {"43543.503206018519", "[$-471]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-471]mmmm dd yyyy h:mm AM/PM aaa", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-471]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-471]mmmmmm dd yyyy h:mm AM/PM dddd", "March 19 2019 12:04 PM Tuesday"}, {"44562.189571759256", "[$-60]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-60]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-60]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-60]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-60]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-60]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"43543.503206018519", "[$-60]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"43543.503206018519", "[$-60]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"44562.189571759256", "[$-460]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-460]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-460]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-460]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-460]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-460]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"43543.503206018519", "[$-460]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"43543.503206018519", "[$-460]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"}, {"44562.189571759256", "[$-860]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-860]mmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"}, {"44562.189571759256", "[$-860]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-860]mmmmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"}, {"43543.503206018519", "[$-860]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-860]mmmm dd yyyy h:mm AM/PM aaa", "March 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-860]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-860]mmmmmm dd yyyy h:mm AM/PM dddd", "March 19 2019 12:04 PM Tuesday"}, {"44562.189571759256", "[$-3F]mmm dd yyyy h:mm AM/PM", "қаң 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3F]mmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3F]mmmmm dd yyyy h:mm AM/PM", "Қ 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3F]mmmmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"}, {"43543.503206018519", "[$-3F]mmm dd yyyy h:mm AM/PM", "нау 19 2019 12:04 PM"}, {"43543.503206018519", "[$-3F]mmmm dd yyyy h:mm AM/PM aaa", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439"}, {"43543.503206018519", "[$-3F]mmmmm dd yyyy h:mm AM/PM ddd", "Н 19 2019 12:04 PM \u0441\u0435\u0439"}, {"43543.503206018519", "[$-3F]mmmmmm dd yyyy h:mm AM/PM dddd", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456"}, {"44562.189571759256", "[$-43F]mmm dd yyyy h:mm AM/PM", "қаң 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43F]mmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43F]mmmmm dd yyyy h:mm AM/PM", "Қ 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43F]mmmmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"}, {"43543.503206018519", "[$-43F]mmm dd yyyy h:mm AM/PM", "нау 19 2019 12:04 PM"}, {"43543.503206018519", "[$-43F]mmmm dd yyyy h:mm AM/PM aaa", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439"}, {"43543.503206018519", "[$-43F]mmmmm dd yyyy h:mm AM/PM ddd", "Н 19 2019 12:04 PM \u0441\u0435\u0439"}, {"43543.503206018519", "[$-43F]mmmmmm dd yyyy h:mm AM/PM dddd", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456"}, {"44562.189571759256", "[$-53]mmm dd yyyy h:mm AM/PM", "\u17E1 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-53]mmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-53]mmmmm dd yyyy h:mm AM/PM", "\u1798 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-53]mmmmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"43543.503206018519", "[$-53]mmm dd yyyy h:mm AM/PM", "\u17E3 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785"}, {"43543.503206018519", "[$-53]mmmm dd yyyy h:mm AM/PM aaa", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."}, {"43543.503206018519", "[$-53]mmmmm dd yyyy h:mm AM/PM ddd", "\u1798 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."}, {"43543.503206018519", "[$-53]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A"}, {"44562.189571759256", "[$-453]mmm dd yyyy h:mm AM/PM", "\u17E1 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-453]mmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-453]mmmmm dd yyyy h:mm AM/PM", "\u1798 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"44562.189571759256", "[$-453]mmmmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"}, {"43543.503206018519", "[$-453]mmm dd yyyy h:mm AM/PM", "\u17E3 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785"}, {"43543.503206018519", "[$-453]mmmm dd yyyy h:mm AM/PM aaa", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."}, {"43543.503206018519", "[$-453]mmmmm dd yyyy h:mm AM/PM ddd", "\u1798 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."}, {"43543.503206018519", "[$-453]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A"}, {"44562.189571759256", "[$-86]mmm dd yyyy h:mm AM/PM", "nab'e 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-86]mmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-86]mmmmm dd yyyy h:mm AM/PM", "n 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-86]mmmmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-86]mmm dd yyyy h:mm AM/PM", "urox 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-86]mmmm dd yyyy h:mm AM/PM aaa", "urox ik' 19 2019 12:04 p.m. oxq'"}, {"43543.503206018519", "[$-86]mmmmm dd yyyy h:mm AM/PM ddd", "u 19 2019 12:04 p.m. oxq'"}, {"43543.503206018519", "[$-86]mmmmmm dd yyyy h:mm AM/PM dddd", "urox ik' 19 2019 12:04 p.m. oxq'ij"}, {"44562.189571759256", "[$-486]mmm dd yyyy h:mm AM/PM", "nab'e 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-486]mmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-486]mmmmm dd yyyy h:mm AM/PM", "n 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-486]mmmmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-486]mmm dd yyyy h:mm AM/PM", "urox 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-486]mmmm dd yyyy h:mm AM/PM aaa", "urox ik' 19 2019 12:04 p.m. oxq'"}, {"43543.503206018519", "[$-486]mmmmm dd yyyy h:mm AM/PM ddd", "u 19 2019 12:04 p.m. oxq'"}, {"43543.503206018519", "[$-486]mmmmmm dd yyyy h:mm AM/PM dddd", "urox ik' 19 2019 12:04 p.m. oxq'ij"}, {"44562.189571759256", "[$-87]mmm dd yyyy h:mm AM/PM", "mut. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-87]mmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"}, {"44562.189571759256", "[$-87]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44562.189571759256", "[$-87]mmmmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"}, {"43543.503206018519", "[$-87]mmm dd yyyy h:mm AM/PM", "wer. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-87]mmmm dd yyyy h:mm AM/PM aaa", "Werurwe 19 2019 12:04 PM kab."}, {"43543.503206018519", "[$-87]mmmmm dd yyyy h:mm AM/PM ddd", "W 19 2019 12:04 PM kab."}, {"43543.503206018519", "[$-87]mmmmmm dd yyyy h:mm AM/PM dddd", "Werurwe 19 2019 12:04 PM Ku wa kabiri"}, {"44562.189571759256", "[$-487]mmm dd yyyy h:mm AM/PM", "mut. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-487]mmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"}, {"44562.189571759256", "[$-487]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44562.189571759256", "[$-487]mmmmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"}, {"43543.503206018519", "[$-487]mmm dd yyyy h:mm AM/PM", "wer. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-487]mmmm dd yyyy h:mm AM/PM aaa", "Werurwe 19 2019 12:04 PM kab."}, {"43543.503206018519", "[$-487]mmmmm dd yyyy h:mm AM/PM ddd", "W 19 2019 12:04 PM kab."}, {"43543.503206018519", "[$-487]mmmmmm dd yyyy h:mm AM/PM dddd", "Werurwe 19 2019 12:04 PM Ku wa kabiri"}, {"44562.189571759256", "[$-41]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-41]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PM"}, {"43543.503206018519", "[$-41]mmmm dd yyyy h:mm AM/PM aaa", "Machi 19 2019 12:04 PM Jnn"}, {"43543.503206018519", "[$-41]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Jnn"}, {"43543.503206018519", "[$-41]mmmmmm dd yyyy h:mm AM/PM dddd", "Machi 19 2019 12:04 PM Jumanne"}, {"44562.189571759256", "[$-441]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-441]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-441]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-441]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-441]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PM"}, {"43543.503206018519", "[$-441]mmmm dd yyyy h:mm AM/PM aaa", "Machi 19 2019 12:04 PM Jnn"}, {"43543.503206018519", "[$-441]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Jnn"}, {"43543.503206018519", "[$-441]mmmmmm dd yyyy h:mm AM/PM dddd", "Machi 19 2019 12:04 PM Jumanne"}, {"44562.189571759256", "[$-57]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-57]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-57]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-57]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"43543.503206018519", "[$-57]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-57]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-57]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-57]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u093E\u0930"}, {"44562.189571759256", "[$-457]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-457]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-457]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-457]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"43543.503206018519", "[$-457]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-457]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-457]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-457]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u093E\u0930"}, {"43543.503206018519", "[$-12]mmm dd yyyy h:mm AM/PM aaa", "3 19 2019 12:04 오후 화"}, {"43543.503206018519", "[$-12]mmmm dd yyyy h:mm AM/PM ddd", "3월 19 2019 12:04 오후 화"}, {"43543.503206018519", "[$-12]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 오후 화요일"}, {"43543.503206018519", "[$-412]mmm dd yyyy h:mm AM/PM aaa", "3 19 2019 12:04 오후 화"}, {"43543.503206018519", "[$-412]mmmm dd yyyy h:mm AM/PM ddd", "3월 19 2019 12:04 오후 화"}, {"43543.503206018519", "[$-412]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 오후 화요일"}, {"44562.189571759256", "[$-40]mmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-40]mmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-40]mmmmm dd yyyy h:mm AM/PM", "\u042F 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-40]mmmmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"}, {"43543.503206018519", "[$-40]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 \u0442\u043A"}, {"43543.503206018519", "[$-40]mmmm dd yyyy h:mm AM/PM aaa", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."}, {"43543.503206018519", "[$-40]mmmmm dd yyyy h:mm AM/PM ddd", "\u041C 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."}, {"43543.503206018519", "[$-40]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438"}, {"44562.189571759256", "[$-440]mmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-440]mmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-440]mmmmm dd yyyy h:mm AM/PM", "\u042F 01 2022 4:32 \u0442\u04A3"}, {"44562.189571759256", "[$-440]mmmmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"}, {"43543.503206018519", "[$-440]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 \u0442\u043A"}, {"43543.503206018519", "[$-440]mmmm dd yyyy h:mm AM/PM aaa", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."}, {"43543.503206018519", "[$-440]mmmmm dd yyyy h:mm AM/PM ddd", "\u041C 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."}, {"43543.503206018519", "[$-440]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438"}, {"44562.189571759256", "[$-54]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E81. 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-54]mmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-54]mmmmm dd yyyy h:mm AM/PM", "\u0EA1 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-54]mmmmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"43543.503206018519", "[$-54]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E99. 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87"}, {"43543.503206018519", "[$-54]mmmm dd yyyy h:mm AM/PM aaa", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"43543.503206018519", "[$-54]mmmmm dd yyyy h:mm AM/PM ddd", "\u0EA1 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"43543.503206018519", "[$-54]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"44562.189571759256", "[$-454]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E81. 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-454]mmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-454]mmmmm dd yyyy h:mm AM/PM", "\u0EA1 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"44562.189571759256", "[$-454]mmmmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"}, {"43543.503206018519", "[$-454]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E99. 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87"}, {"43543.503206018519", "[$-454]mmmm dd yyyy h:mm AM/PM aaa", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"43543.503206018519", "[$-454]mmmmm dd yyyy h:mm AM/PM ddd", "\u0EA1 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"43543.503206018519", "[$-454]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"}, {"44562.189571759256", "[$-476]mmm dd yyyy h:mm AM/PM", "Ian 01 2022 4:32 AM"}, {"44562.189571759256", "[$-476]mmmm dd yyyy h:mm AM/PM", "Ianuarius 01 2022 4:32 AM"}, {"44562.189571759256", "[$-476]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"}, {"44562.189571759256", "[$-476]mmmmmm dd yyyy h:mm AM/PM", "Ianuarius 01 2022 4:32 AM"}, {"43543.503206018519", "[$-476]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-476]mmmm dd yyyy h:mm AM/PM aaa", "Martius 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-476]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Mar"}, {"43543.503206018519", "[$-476]mmmmmm dd yyyy h:mm AM/PM dddd", "Martius 19 2019 12:04 PM Martis"}, {"44562.189571759256", "[$-26]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-26]mmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-26]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-26]mmmmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."}, {"43543.503206018519", "[$-26]mmm dd yyyy h:mm AM/PM", "marts 19 2019 12:04 pēcp."}, {"43543.503206018519", "[$-26]mmmm dd yyyy h:mm AM/PM aaa", "marts 19 2019 12:04 pēcp. otrd."}, {"43543.503206018519", "[$-26]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pēcp. otrd."}, {"43543.503206018519", "[$-26]mmmmmm dd yyyy h:mm AM/PM dddd", "marts 19 2019 12:04 pēcp. otrdiena"}, {"44562.189571759256", "[$-426]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-426]mmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-426]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 priekšp."}, {"44562.189571759256", "[$-426]mmmmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."}, {"43543.503206018519", "[$-426]mmm dd yyyy h:mm AM/PM", "marts 19 2019 12:04 pēcp."}, {"43543.503206018519", "[$-426]mmmm dd yyyy h:mm AM/PM aaa", "marts 19 2019 12:04 pēcp. otrd."}, {"43543.503206018519", "[$-426]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pēcp. otrd."}, {"43543.503206018519", "[$-426]mmmmmm dd yyyy h:mm AM/PM dddd", "marts 19 2019 12:04 pēcp. otrdiena"}, {"44562.189571759256", "[$-27]mmm dd yyyy h:mm AM/PM", "saus. 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-27]mmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-27]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-27]mmmmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"}, {"43543.503206018519", "[$-27]mmm dd yyyy h:mm AM/PM", "kov. 19 2019 12:04 popiet"}, {"43543.503206018519", "[$-27]mmmm dd yyyy h:mm AM/PM aaa", "kovas 19 2019 12:04 popiet an"}, {"43543.503206018519", "[$-27]mmmmm dd yyyy h:mm AM/PM ddd", "k 19 2019 12:04 popiet an"}, {"43543.503206018519", "[$-27]mmmmmm dd yyyy h:mm AM/PM dddd", "kovas 19 2019 12:04 popiet antradienis"}, {"44562.189571759256", "[$-427]mmm dd yyyy h:mm AM/PM", "saus. 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-427]mmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-427]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 priešpiet"}, {"44562.189571759256", "[$-427]mmmmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"}, {"43543.503206018519", "[$-427]mmm dd yyyy h:mm AM/PM", "kov. 19 2019 12:04 popiet"}, {"43543.503206018519", "[$-427]mmmm dd yyyy h:mm AM/PM aaa", "kovas 19 2019 12:04 popiet an"}, {"43543.503206018519", "[$-427]mmmmm dd yyyy h:mm AM/PM ddd", "k 19 2019 12:04 popiet an"}, {"43543.503206018519", "[$-427]mmmmmm dd yyyy h:mm AM/PM dddd", "kovas 19 2019 12:04 popiet antradienis"}, {"44562.189571759256", "[$-7C2E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C2E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C2E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C2E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C2E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C2E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 PM wa\u0142"}, {"43543.503206018519", "[$-7C2E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wa\u0142"}, {"43543.503206018519", "[$-7C2E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 PM wa\u0142tora"}, {"44562.189571759256", "[$-82E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-82E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 PM"}, {"43543.503206018519", "[$-82E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 PM wa\u0142"}, {"43543.503206018519", "[$-82E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wa\u0142"}, {"43543.503206018519", "[$-82E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 PM wa\u0142tora"}, {"44562.189571759256", "[$-6E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6E]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6E]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-6E]mmm dd yyyy h:mm AM/PM", "Mäe 19 2019 12:04 PM"}, {"43543.503206018519", "[$-6E]mmmm dd yyyy h:mm AM/PM aaa", "Mäerz 19 2019 12:04 PM Dën"}, {"43543.503206018519", "[$-6E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Dën"}, {"43543.503206018519", "[$-6E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mäerz 19 2019 12:04 PM Dënschdeg"}, {"44562.189571759256", "[$-46E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46E]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46E]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-46E]mmm dd yyyy h:mm AM/PM", "Mäe 19 2019 12:04 PM"}, {"43543.503206018519", "[$-46E]mmmm dd yyyy h:mm AM/PM aaa", "Mäerz 19 2019 12:04 PM Dën"}, {"43543.503206018519", "[$-46E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Dën"}, {"43543.503206018519", "[$-46E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mäerz 19 2019 12:04 PM Dënschdeg"}, {"44562.189571759256", "[$-2F]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-2F]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-2F]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-2F]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"43543.503206018519", "[$-2F]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 \u043F\u043E\u043F\u043B."}, {"43543.503206018519", "[$-2F]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."}, {"43543.503206018519", "[$-2F]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."}, {"43543.503206018519", "[$-2F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"44562.189571759256", "[$-42F]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-42F]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-42F]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"44562.189571759256", "[$-42F]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."}, {"43543.503206018519", "[$-42F]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 \u043F\u043E\u043F\u043B."}, {"43543.503206018519", "[$-42F]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."}, {"43543.503206018519", "[$-42F]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."}, {"43543.503206018519", "[$-42F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"44562.189571759256", "[$-3E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"}, {"44562.189571759256", "[$-3E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"44562.189571759256", "[$-3E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"}, {"44562.189571759256", "[$-3E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"43543.503206018519", "[$-3E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"}, {"43543.503206018519", "[$-3E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-3E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-3E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"}, {"44562.189571759256", "[$-83E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"}, {"44562.189571759256", "[$-83E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"44562.189571759256", "[$-83E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"}, {"44562.189571759256", "[$-83E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"43543.503206018519", "[$-83E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"}, {"43543.503206018519", "[$-83E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-83E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-83E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"}, {"44562.189571759256", "[$-43E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"}, {"44562.189571759256", "[$-43E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"44562.189571759256", "[$-43E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"}, {"44562.189571759256", "[$-43E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"}, {"43543.503206018519", "[$-43E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"}, {"43543.503206018519", "[$-43E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-43E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"}, {"43543.503206018519", "[$-43E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"}, {"44562.189571759256", "[$-4C]mmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4C]mmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4C]mmmmm dd yyyy h:mm AM/PM", "\u0D1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4C]mmmmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"}, {"43543.503206018519", "[$-4C]mmm dd yyyy h:mm AM/PM", "\u0D2E\u0D3E\u0D7C 19 2019 12:04 PM"}, {"43543.503206018519", "[$-4C]mmmm dd yyyy h:mm AM/PM aaa", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"}, {"43543.503206018519", "[$-4C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0D2E 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"}, {"43543.503206018519", "[$-4C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A"}, {"44562.189571759256", "[$-44C]mmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44C]mmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44C]mmmmm dd yyyy h:mm AM/PM", "\u0D1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44C]mmmmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"}, {"43543.503206018519", "[$-44C]mmm dd yyyy h:mm AM/PM", "\u0D2E\u0D3E\u0D7C 19 2019 12:04 PM"}, {"43543.503206018519", "[$-44C]mmmm dd yyyy h:mm AM/PM aaa", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"}, {"43543.503206018519", "[$-44C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0D2E 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"}, {"43543.503206018519", "[$-44C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A"}, {"44562.189571759256", "[$-3A]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3A]mmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3A]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-3A]mmmmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-3A]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-3A]mmmm dd yyyy h:mm AM/PM aaa", "Marzu 19 2019 12:04 PM Tli"}, {"43543.503206018519", "[$-3A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tli"}, {"43543.503206018519", "[$-3A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzu 19 2019 12:04 PM It-Tlieta"}, {"44562.189571759256", "[$-43A]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43A]mmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43A]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-43A]mmmmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-43A]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-43A]mmmm dd yyyy h:mm AM/PM aaa", "Marzu 19 2019 12:04 PM Tli"}, {"43543.503206018519", "[$-43A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tli"}, {"43543.503206018519", "[$-43A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzu 19 2019 12:04 PM It-Tlieta"}, {"44562.189571759256", "[$-81]mmm dd yyyy h:mm AM/PM", "Kohi 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-81]mmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-81]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-81]mmmmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-81]mmm dd yyyy h:mm AM/PM", "Pou 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-81]mmmm dd yyyy h:mm AM/PM aaa", "Poutūterangi 19 2019 12:04 p.m. Tū"}, {"43543.503206018519", "[$-81]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Tū"}, {"43543.503206018519", "[$-81]mmmmmm dd yyyy h:mm AM/PM dddd", "Poutūterangi 19 2019 12:04 p.m. Rātū"}, {"44562.189571759256", "[$-481]mmm dd yyyy h:mm AM/PM", "Kohi 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-481]mmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-481]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-481]mmmmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-481]mmm dd yyyy h:mm AM/PM", "Pou 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-481]mmmm dd yyyy h:mm AM/PM aaa", "Poutūterangi 19 2019 12:04 p.m. Tū"}, {"43543.503206018519", "[$-481]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Tū"}, {"43543.503206018519", "[$-481]mmmmmm dd yyyy h:mm AM/PM dddd", "Poutūterangi 19 2019 12:04 p.m. Rātū"}, {"44562.189571759256", "[$-7A]mmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7A]mmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7A]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7A]mmmmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7A]mmm dd yyyy h:mm AM/PM", "Kila 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7A]mmmm dd yyyy h:mm AM/PM aaa", "Kila 19 2019 12:04 PM Kila"}, {"43543.503206018519", "[$-7A]mmmmm dd yyyy h:mm AM/PM ddd", "K 19 2019 12:04 PM Kila"}, {"43543.503206018519", "[$-7A]mmmmmm dd yyyy h:mm AM/PM dddd", "Kila 19 2019 12:04 PM Kila Ante"}, {"44562.189571759256", "[$-47A]mmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47A]mmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47A]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47A]mmmmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-47A]mmm dd yyyy h:mm AM/PM", "Kila 19 2019 12:04 PM"}, {"43543.503206018519", "[$-47A]mmmm dd yyyy h:mm AM/PM aaa", "Kila 19 2019 12:04 PM Kila"}, {"43543.503206018519", "[$-47A]mmmmm dd yyyy h:mm AM/PM ddd", "K 19 2019 12:04 PM Kila"}, {"43543.503206018519", "[$-47A]mmmmmm dd yyyy h:mm AM/PM dddd", "Kila 19 2019 12:04 PM Kila Ante"}, {"44562.189571759256", "[$-4E]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947. 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-4E]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-4E]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-4E]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"43543.503206018519", "[$-4E]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-4E]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-4E]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-4E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u0935\u093E\u0930"}, {"44562.189571759256", "[$-44E]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947. 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-44E]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-44E]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."}, {"44562.189571759256", "[$-44E]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."}, {"43543.503206018519", "[$-44E]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-44E]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-44E]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."}, {"43543.503206018519", "[$-44E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u0935\u093E\u0930"}, {"43543.503206018519", "[$-44E]mmmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-44E]mmmmm dd yyyy h:mm AM/PM", "\u092E 19 2019 12:04 \u092E.\u0928\u0902."}, {"43543.503206018519", "[$-44E]mmmmmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."}, {"44562.189571759256", "[$-7C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C]mmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C]mmmmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C]mmmm dd yyyy h:mm AM/PM aaa", "Enniskó:Wa 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-7C]mmmmm dd yyyy h:mm AM/PM ddd", "E 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-7C]mmmmmm dd yyyy h:mm AM/PM dddd", "Enniskó:Wa 19 2019 12:04 PM Ratironhia'kehronòn:ke"}, {"44562.189571759256", "[$-47C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47C]mmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47C]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 AM"}, {"44562.189571759256", "[$-47C]mmmmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"}, {"43543.503206018519", "[$-47C]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-47C]mmmm dd yyyy h:mm AM/PM aaa", "Enniskó:Wa 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-47C]mmmmm dd yyyy h:mm AM/PM ddd", "E 19 2019 12:04 PM Tue"}, {"43543.503206018519", "[$-47C]mmmmmm dd yyyy h:mm AM/PM dddd", "Enniskó:Wa 19 2019 12:04 PM Ratironhia'kehronòn:ke"}, {"44562.189571759256", "[$-50]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-50]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-50]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-50]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"43543.503206018519", "[$-50]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."}, {"43543.503206018519", "[$-50]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-50]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-50]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"}, {"44562.189571759256", "[$-7850]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-7850]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-7850]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-7850]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"43543.503206018519", "[$-7850]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."}, {"43543.503206018519", "[$-7850]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-7850]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-7850]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"}, {"44562.189571759256", "[$-450]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-450]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-450]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."}, {"44562.189571759256", "[$-450]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."}, {"43543.503206018519", "[$-450]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."}, {"43543.503206018519", "[$-450]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-450]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"}, {"43543.503206018519", "[$-450]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"}, {"44562.189571759256", "[$-7C50]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-7C50]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C50]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-7C50]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"}, {"44562.189571759256", "[$-7C50]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828"}, {"44896.18957170139", "[$-7C50]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"}, {"44562.189571759256", "[$-850]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-850]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"}, {"44562.189571759256", "[$-850]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-850]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"}, {"44562.189571759256", "[$-850]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828"}, {"44896.18957170139", "[$-850]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"}, {"44562.189571759256", "[$-C50]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-C50]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C50]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"}, {"44896.18957170139", "[$-C50]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182B\u1826\u1837\u182A\u1826"}, {"44562.189571759256", "[$-C50]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182A\u1822\u182E\u182A\u1820"}, {"44896.18957170139", "[$-C50]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182B\u1826\u1837\u182A\u1826"}, {"44562.189571759256", "[$-61]mmm dd yyyy h:mm AM/PM", "\u091C\u0928 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-61]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-61]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-61]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-61]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-61]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-61]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-61]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930"}, {"44562.189571759256", "[$-861]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-861]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-861]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-861]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-861]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-861]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-861]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-861]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u092C\u093E\u0930"}, {"44562.189571759256", "[$-461]mmm dd yyyy h:mm AM/PM", "\u091C\u0928 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-461]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-461]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"44562.189571759256", "[$-461]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-461]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"}, {"43543.503206018519", "[$-461]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-461]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"}, {"43543.503206018519", "[$-461]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930"}, {"44562.189571759256", "[$-14]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-14]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-14]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-14]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-14]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-14]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir."}, {"43543.503206018519", "[$-14]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir."}, {"43543.503206018519", "[$-14]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"}, {"44562.189571759256", "[$-7C14]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-7C14]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-7C14]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-7C14]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-7C14]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-7C14]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir"}, {"43543.503206018519", "[$-7C14]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir"}, {"43543.503206018519", "[$-7C14]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"}, {"44562.189571759256", "[$-414]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-414]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-414]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-414]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-414]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-414]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir"}, {"43543.503206018519", "[$-414]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir"}, {"43543.503206018519", "[$-414]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"}, {"44562.189571759256", "[$-7814]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-7814]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-7814]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-7814]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."}, {"43543.503206018519", "[$-7814]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 e.m."}, {"43543.503206018519", "[$-7814]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.m. tys"}, {"43543.503206018519", "[$-7814]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.m. tys"}, {"43543.503206018519", "[$-7814]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.m. tysdag"}, {"44562.189571759256", "[$-814]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-814]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-814]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.m."}, {"44562.189571759256", "[$-814]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."}, {"43543.503206018519", "[$-814]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 e.m."}, {"43543.503206018519", "[$-814]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.m. tys"}, {"43543.503206018519", "[$-814]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.m. tys"}, {"43543.503206018519", "[$-814]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.m. tysdag"}, {"44562.189571759256", "[$-82]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82]mmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44562.189571759256", "[$-82]mmmmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"}, {"43543.503206018519", "[$-82]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 PM"}, {"43543.503206018519", "[$-82]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 PM dma."}, {"43543.503206018519", "[$-82]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM dma."}, {"43543.503206018519", "[$-82]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 PM dimarts"}, {"44562.189571759256", "[$-482]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-482]mmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"}, {"44562.189571759256", "[$-482]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"}, {"44562.189571759256", "[$-482]mmmmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"}, {"43543.503206018519", "[$-482]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 PM"}, {"43543.503206018519", "[$-482]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 PM dma."}, {"43543.503206018519", "[$-482]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM dma."}, {"43543.503206018519", "[$-482]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 PM dimarts"}, {"44562.189571759256", "[$-48]mmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"44562.189571759256", "[$-48]mmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"44562.189571759256", "[$-48]mmmmm dd yyyy h:mm AM/PM", "\u0B1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-48]mmmmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"43543.503206018519", "[$-48]mmm dd yyyy h:mm AM/PM", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM"}, {"43543.503206018519", "[$-48]mmmm dd yyyy h:mm AM/PM aaa", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."}, {"43543.503206018519", "[$-48]mmmmm dd yyyy h:mm AM/PM ddd", "\u0B2E 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."}, {"43543.503206018519", "[$-48]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30"}, {"44562.189571759256", "[$-448]mmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"44562.189571759256", "[$-448]mmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"44562.189571759256", "[$-448]mmmmm dd yyyy h:mm AM/PM", "\u0B1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-448]mmmmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"}, {"43543.503206018519", "[$-448]mmm dd yyyy h:mm AM/PM", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM"}, {"43543.503206018519", "[$-448]mmmm dd yyyy h:mm AM/PM aaa", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."}, {"43543.503206018519", "[$-448]mmmmm dd yyyy h:mm AM/PM ddd", "\u0B2E 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."}, {"43543.503206018519", "[$-448]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30"}, {"44562.189571759256", "[$-72]mmm dd yyyy h:mm AM/PM", "Ama 01 2022 4:32 WD"}, {"44562.189571759256", "[$-72]mmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"}, {"44562.189571759256", "[$-72]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 WD"}, {"44562.189571759256", "[$-72]mmmmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"}, {"43543.503206018519", "[$-72]mmm dd yyyy h:mm AM/PM", "Bit 19 2019 12:04 WB"}, {"43543.503206018519", "[$-72]mmmm dd yyyy h:mm AM/PM aaa", "Bitooteessa 19 2019 12:04 WB Qib"}, {"43543.503206018519", "[$-72]mmmmm dd yyyy h:mm AM/PM ddd", "B 19 2019 12:04 WB Qib"}, {"43543.503206018519", "[$-72]mmmmmm dd yyyy h:mm AM/PM dddd", "Bitooteessa 19 2019 12:04 WB Qibxata"}, {"44562.189571759256", "[$-472]mmm dd yyyy h:mm AM/PM", "Ama 01 2022 4:32 WD"}, {"44562.189571759256", "[$-472]mmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"}, {"44562.189571759256", "[$-472]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 WD"}, {"44562.189571759256", "[$-472]mmmmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"}, {"43543.503206018519", "[$-472]mmm dd yyyy h:mm AM/PM", "Bit 19 2019 12:04 WB"}, {"43543.503206018519", "[$-472]mmmm dd yyyy h:mm AM/PM aaa", "Bitooteessa 19 2019 12:04 WB Qib"}, {"43543.503206018519", "[$-472]mmmmm dd yyyy h:mm AM/PM ddd", "B 19 2019 12:04 WB Qib"}, {"43543.503206018519", "[$-472]mmmmmm dd yyyy h:mm AM/PM dddd", "Bitooteessa 19 2019 12:04 WB Qibxata"}, {"44562.189571759256", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-63]mmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-63]mmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-63]mmmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-63]mmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-63]mmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-63]mmmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"43543.503206018519", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648."}, {"43543.503206018519", "[$-63]mmmm dd yyyy h:mm AM/PM aaa", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"43543.503206018519", "[$-63]mmmmm dd yyyy h:mm AM/PM ddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"43543.503206018519", "[$-63]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"44562.189571759256", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-463]mmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-463]mmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44562.189571759256", "[$-463]mmmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-463]mmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-463]mmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"44713.188888888886", "[$-463]mmmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."}, {"43543.503206018519", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648."}, {"43543.503206018519", "[$-463]mmmm dd yyyy h:mm AM/PM aaa", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"43543.503206018519", "[$-463]mmmmm dd yyyy h:mm AM/PM ddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"43543.503206018519", "[$-463]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"}, {"44562.189571759256", "[$-29]mmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-29]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-29]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-29]mmmmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"43543.503206018519", "[$-29]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638"}, {"43543.503206018519", "[$-29]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"43543.503206018519", "[$-29]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"43543.503206018519", "[$-29]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"44562.189571759256", "[$-429]mmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-429]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-429]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"}, {"44562.189571759256", "[$-429]mmmmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"}, {"43543.503206018519", "[$-429]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638"}, {"43543.503206018519", "[$-429]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"43543.503206018519", "[$-429]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"43543.503206018519", "[$-429]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"}, {"44562.189571759256", "[$-15]mmm dd yyyy h:mm AM/PM", "sty 01 2022 4:32 AM"}, {"44562.189571759256", "[$-15]mmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"}, {"44562.189571759256", "[$-15]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-15]mmmmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"}, {"43543.503206018519", "[$-15]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-15]mmmm dd yyyy h:mm AM/PM aaa", "marzec 19 2019 12:04 PM wt."}, {"43543.503206018519", "[$-15]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wt."}, {"43543.503206018519", "[$-15]mmmmmm dd yyyy h:mm AM/PM dddd", "marzec 19 2019 12:04 PM wtorek"}, {"44562.189571759256", "[$-415]mmm dd yyyy h:mm AM/PM", "sty 01 2022 4:32 AM"}, {"44562.189571759256", "[$-415]mmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"}, {"44562.189571759256", "[$-415]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-415]mmmmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"}, {"43543.503206018519", "[$-415]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-415]mmmm dd yyyy h:mm AM/PM aaa", "marzec 19 2019 12:04 PM wt."}, {"43543.503206018519", "[$-415]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wt."}, {"43543.503206018519", "[$-415]mmmmmm dd yyyy h:mm AM/PM dddd", "marzec 19 2019 12:04 PM wtorek"}, {"44562.189571759256", "[$-16]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-16]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-16]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-16]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"43543.503206018519", "[$-16]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-16]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-16]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-16]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"}, {"44562.189571759256", "[$-416]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-416]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-416]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-416]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"43543.503206018519", "[$-416]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-416]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-416]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-416]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"}, {"44562.189571759256", "[$-816]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-816]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"44562.189571759256", "[$-816]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-816]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"}, {"43543.503206018519", "[$-816]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-816]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-816]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"}, {"43543.503206018519", "[$-816]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"}, {"44562.189571759256", "[$-46]mmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-46]mmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-46]mmmmm dd yyyy h:mm AM/PM", "\u0A1C 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-46]mmmmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"43543.503206018519", "[$-46]mmm dd yyyy h:mm AM/PM", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E"}, {"43543.503206018519", "[$-46]mmmm dd yyyy h:mm AM/PM aaa", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."}, {"43543.503206018519", "[$-46]mmmmm dd yyyy h:mm AM/PM ddd", "\u0A2E 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."}, {"43543.503206018519", "[$-46]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30"}, {"44562.189571759256", "[$-7C46]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C46]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C46]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C46]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C46]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C46]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"43543.503206018519", "[$-7C46]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"43543.503206018519", "[$-7C46]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"44562.189571759256", "[$-446]mmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-446]mmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-446]mmmmm dd yyyy h:mm AM/PM", "\u0A1C 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"44562.189571759256", "[$-446]mmmmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"}, {"43543.503206018519", "[$-446]mmm dd yyyy h:mm AM/PM", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E"}, {"43543.503206018519", "[$-446]mmmm dd yyyy h:mm AM/PM aaa", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."}, {"43543.503206018519", "[$-446]mmmmm dd yyyy h:mm AM/PM ddd", "\u0A2E 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."}, {"43543.503206018519", "[$-446]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30"}, {"44562.189571759256", "[$-846]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-846]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-846]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-846]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-846]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-846]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"43543.503206018519", "[$-846]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"43543.503206018519", "[$-846]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"}, {"44562.189571759256", "[$-6B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-6B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-6B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-6B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-6B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-6B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. ati"}, {"43543.503206018519", "[$-6B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. ati"}, {"43543.503206018519", "[$-6B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. atipachaw"}, {"44562.189571759256", "[$-46B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-46B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-46B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-46B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-46B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-46B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. ati"}, {"43543.503206018519", "[$-46B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. ati"}, {"43543.503206018519", "[$-46B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. atipachaw"}, {"44562.189571759256", "[$-86B]mmm dd yyyy h:mm AM/PM", "kull 01 2022 4:32 AM"}, {"44562.189571759256", "[$-86B]mmmm dd yyyy h:mm AM/PM", "kulla 01 2022 4:32 AM"}, {"44562.189571759256", "[$-86B]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"}, {"44562.189571759256", "[$-86B]mmmmmm dd yyyy h:mm AM/PM", "kulla 01 2022 4:32 AM"}, {"43543.503206018519", "[$-86B]mmm dd yyyy h:mm AM/PM", "paw 19 2019 12:04 PM"}, {"43543.503206018519", "[$-86B]mmmm dd yyyy h:mm AM/PM aaa", "pawkar 19 2019 12:04 PM wan"}, {"43543.503206018519", "[$-86B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM wan"}, {"43543.503206018519", "[$-86B]mmmmmm dd yyyy h:mm AM/PM dddd", "pawkar 19 2019 12:04 PM wanra"}, {"44562.189571759256", "[$-C6B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-C6B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-C6B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-C6B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-C6B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-C6B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. Mar"}, {"43543.503206018519", "[$-C6B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Mar"}, {"43543.503206018519", "[$-C6B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. Martes"}, {"44562.189571759256", "[$-18]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-18]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-18]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-18]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-18]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-18]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-18]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-18]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"}, {"44562.189571759256", "[$-818]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-818]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-818]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-818]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-818]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-818]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. Mar"}, {"43543.503206018519", "[$-818]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. Mar"}, {"43543.503206018519", "[$-818]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"}, {"44562.189571759256", "[$-418]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-418]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-418]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-418]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-418]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."}, {"43543.503206018519", "[$-418]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-418]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-418]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"}, {"44562.189571759256", "[$-17]mmm dd yyyy h:mm AM/PM", "schan. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-17]mmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"}, {"44562.189571759256", "[$-17]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-17]mmmmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"}, {"43543.503206018519", "[$-17]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"}, {"43543.503206018519", "[$-17]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM ma"}, {"43543.503206018519", "[$-17]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ma"}, {"43543.503206018519", "[$-17]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-417]mmm dd yyyy h:mm AM/PM", "schan. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-417]mmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"}, {"44562.189571759256", "[$-417]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"}, {"44562.189571759256", "[$-417]mmmmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"}, {"43543.503206018519", "[$-417]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"}, {"43543.503206018519", "[$-417]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM ma"}, {"43543.503206018519", "[$-417]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ma"}, {"43543.503206018519", "[$-417]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM mardi"}, {"44562.189571759256", "[$-19]mmm dd yyyy h:mm AM/PM", "янв. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-19]mmmm dd yyyy h:mm AM/PM", "январь 01 2022 4:32 AM"}, {"44562.189571759256", "[$-19]mmmmm dd yyyy h:mm AM/PM", "я 01 2022 4:32 AM"}, {"43543.503206018519", "[$-19]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-19]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-19]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"43543.503206018519", "[$-819]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-819]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-819]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"43543.503206018519", "[$-419]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-419]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-419]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"}, {"44562.189571759256", "[$-85]mmm dd yyyy h:mm AM/PM", "\u0422\u0445\u0441 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-85]mmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-85]mmmmm dd yyyy h:mm AM/PM", "\u0422 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-85]mmmmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"}, {"43543.503206018519", "[$-85]mmm dd yyyy h:mm AM/PM", "\u041A\u043B\u043D 19 2019 12:04 \u041A\u041A"}, {"43543.503206018519", "[$-85]mmmm dd yyyy h:mm AM/PM aaa", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u043E\u043F"}, {"43543.503206018519", "[$-85]mmmmm dd yyyy h:mm AM/PM ddd", "\u041A 19 2019 12:04 \u041A\u041A \u043E\u043F"}, {"43543.503206018519", "[$-85]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A"}, {"44562.189571759256", "[$-485]mmm dd yyyy h:mm AM/PM", "\u0422\u0445\u0441 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-485]mmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-485]mmmmm dd yyyy h:mm AM/PM", "\u0422 01 2022 4:32 \u041A\u0418"}, {"44562.189571759256", "[$-485]mmmmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"}, {"43543.503206018519", "[$-485]mmm dd yyyy h:mm AM/PM", "\u041A\u043B\u043D 19 2019 12:04 \u041A\u041A"}, {"43543.503206018519", "[$-485]mmmm dd yyyy h:mm AM/PM aaa", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u043E\u043F"}, {"43543.503206018519", "[$-485]mmmmm dd yyyy h:mm AM/PM ddd", "\u041A 19 2019 12:04 \u041A\u041A \u043E\u043F"}, {"43543.503206018519", "[$-485]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A"}, {"44562.189571759256", "[$-703B]mmm dd yyyy h:mm AM/PM", "uđiv 01 2022 4:32 AM"}, {"44562.189571759256", "[$-703B]mmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-703B]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44562.189571759256", "[$-703B]mmmmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-703B]mmm dd yyyy h:mm AM/PM", "njuh 19 2019 12:04 PM"}, {"43543.503206018519", "[$-703B]mmmm dd yyyy h:mm AM/PM aaa", "njuhčâmáánu 19 2019 12:04 PM maj"}, {"43543.503206018519", "[$-703B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM maj"}, {"43543.503206018519", "[$-703B]mmmmmm dd yyyy h:mm AM/PM dddd", "njuhčâmáánu 19 2019 12:04 PM majebargâ"}, {"44562.189571759256", "[$-243B]mmm dd yyyy h:mm AM/PM", "uđiv 01 2022 4:32 AM"}, {"44562.189571759256", "[$-243B]mmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-243B]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44562.189571759256", "[$-243B]mmmmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-243B]mmm dd yyyy h:mm AM/PM", "njuh 19 2019 12:04 PM"}, {"43543.503206018519", "[$-243B]mmmm dd yyyy h:mm AM/PM aaa", "njuhčâmáánu 19 2019 12:04 PM maj"}, {"43543.503206018519", "[$-243B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM maj"}, {"43543.503206018519", "[$-243B]mmmmmm dd yyyy h:mm AM/PM dddd", "njuhčâmáánu 19 2019 12:04 PM majebargâ"}, {"44562.189571759256", "[$-7C3B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C3B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C3B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C3B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C3B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C3B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-7C3B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-7C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"}, {"44562.189571759256", "[$-103B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"}, {"44562.189571759256", "[$-103B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"44562.189571759256", "[$-103B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"}, {"44562.189571759256", "[$-103B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"43543.503206018519", "[$-103B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"}, {"43543.503206018519", "[$-103B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-103B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-103B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"}, {"44562.189571759256", "[$-143B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"}, {"44562.189571759256", "[$-143B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"44562.189571759256", "[$-143B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"}, {"44562.189571759256", "[$-143B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"}, {"43543.503206018519", "[$-143B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"}, {"43543.503206018519", "[$-143B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-143B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-143B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"}, {"44562.189571759256", "[$-3B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-3B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-3B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-3B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."}, {"43543.503206018519", "[$-3B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 e.b."}, {"43543.503206018519", "[$-3B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 e.b. maŋ"}, {"43543.503206018519", "[$-3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 e.b. maŋ"}, {"43543.503206018519", "[$-3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 e.b. maŋŋebárga"}, {"44562.189571759256", "[$-C3B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C3B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C3B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C3B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-C3B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 PM"}, {"43543.503206018519", "[$-C3B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 PM di"}, {"43543.503206018519", "[$-C3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM di"}, {"43543.503206018519", "[$-C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 PM maŋŋebárga"}, {"44562.189571759256", "[$-43B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-43B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-43B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 i.b."}, {"44562.189571759256", "[$-43B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."}, {"43543.503206018519", "[$-43B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 e.b."}, {"43543.503206018519", "[$-43B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 e.b. maŋ"}, {"43543.503206018519", "[$-43B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 e.b. maŋ"}, {"43543.503206018519", "[$-43B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 e.b. maŋŋebárga"}, {"44562.189571759256", "[$-83B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 AM"}, {"44562.189571759256", "[$-83B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 AM"}, {"44562.189571759256", "[$-83B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44562.189571759256", "[$-83B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 AM"}, {"43543.503206018519", "[$-83B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 PM"}, {"43543.503206018519", "[$-83B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-83B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dis"}, {"43543.503206018519", "[$-83B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 PM disdat"}, {"44562.189571759256", "[$-743B]mmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"44562.189571759256", "[$-743B]mmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"44562.189571759256", "[$-743B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44562.189571759256", "[$-743B]mmmmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"43543.503206018519", "[$-743B]mmm dd yyyy h:mm AM/PM", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM"}, {"43543.503206018519", "[$-743B]mmmm dd yyyy h:mm AM/PM aaa", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mâ"}, {"43543.503206018519", "[$-743B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM mâ"}, {"43543.503206018519", "[$-743B]mmmmmm dd yyyy h:mm AM/PM dddd", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mââibargg"}, {"44562.189571759256", "[$-203B]mmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"44562.189571759256", "[$-203B]mmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"44562.189571759256", "[$-203B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"}, {"44562.189571759256", "[$-203B]mmmmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"}, {"43543.503206018519", "[$-203B]mmm dd yyyy h:mm AM/PM", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM"}, {"43543.503206018519", "[$-203B]mmmm dd yyyy h:mm AM/PM aaa", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mâ"}, {"43543.503206018519", "[$-203B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM mâ"}, {"43543.503206018519", "[$-203B]mmmmmm dd yyyy h:mm AM/PM dddd", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mââibargg"}, {"44562.189571759256", "[$-783B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-783B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"44562.189571759256", "[$-783B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44562.189571759256", "[$-783B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"43543.503206018519", "[$-783B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"}, {"43543.503206018519", "[$-783B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-783B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-783B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"}, {"44562.189571759256", "[$-183B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-183B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"44562.189571759256", "[$-183B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44562.189571759256", "[$-183B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"43543.503206018519", "[$-183B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"}, {"43543.503206018519", "[$-183B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-183B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-183B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"}, {"44562.189571759256", "[$-1C3B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C3B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C3B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C3B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"}, {"43543.503206018519", "[$-1C3B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"}, {"43543.503206018519", "[$-1C3B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-1C3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"}, {"43543.503206018519", "[$-1C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"}, {"44562.189571759256", "[$-4F]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-4F]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-4F]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-4F]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"43543.503206018519", "[$-4F]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924"}, {"43543.503206018519", "[$-4F]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"}, {"43543.503206018519", "[$-4F]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"}, {"43543.503206018519", "[$-4F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903"}, {"44562.189571759256", "[$-44F]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-44F]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-44F]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"44562.189571759256", "[$-44F]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"}, {"43543.503206018519", "[$-44F]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924"}, {"43543.503206018519", "[$-44F]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"}, {"43543.503206018519", "[$-44F]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"}, {"43543.503206018519", "[$-44F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903"}, {"44562.189571759256", "[$-91]mmm dd yyyy h:mm AM/PM", "Faoi 01 2022 4:32 m"}, {"44562.189571759256", "[$-91]mmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"}, {"44562.189571759256", "[$-91]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 m"}, {"44562.189571759256", "[$-91]mmmmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"}, {"43543.503206018519", "[$-91]mmm dd yyyy h:mm AM/PM", "Màrt 19 2019 12:04 f"}, {"43543.503206018519", "[$-91]mmmm dd yyyy h:mm AM/PM aaa", "Am Màrt 19 2019 12:04 f DiM"}, {"43543.503206018519", "[$-91]mmmmm dd yyyy h:mm AM/PM ddd", "A 19 2019 12:04 f DiM"}, {"43543.503206018519", "[$-91]mmmmmm dd yyyy h:mm AM/PM dddd", "Am Màrt 19 2019 12:04 f DiMàirt"}, {"44562.189571759256", "[$-491]mmm dd yyyy h:mm AM/PM", "Faoi 01 2022 4:32 m"}, {"44562.189571759256", "[$-491]mmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"}, {"44562.189571759256", "[$-491]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 m"}, {"44562.189571759256", "[$-491]mmmmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"}, {"43543.503206018519", "[$-491]mmm dd yyyy h:mm AM/PM", "Màrt 19 2019 12:04 f"}, {"43543.503206018519", "[$-491]mmmm dd yyyy h:mm AM/PM aaa", "Am Màrt 19 2019 12:04 f DiM"}, {"43543.503206018519", "[$-491]mmmmm dd yyyy h:mm AM/PM ddd", "A 19 2019 12:04 f DiM"}, {"43543.503206018519", "[$-491]mmmmmm dd yyyy h:mm AM/PM dddd", "Am Màrt 19 2019 12:04 f DiMàirt"}, {"44562.189571759256", "[$-6C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-6C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"}, {"43543.503206018519", "[$-6C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-6C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-6C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-1C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-1C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-1C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-1C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-1C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-301A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-301A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-301A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-301A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-301A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"}, {"43543.503206018519", "[$-301A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-301A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"}, {"43543.503206018519", "[$-301A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-281A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-281A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-281A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-281A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-281A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"}, {"43543.503206018519", "[$-281A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-281A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-281A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"}, {"44562.189571759256", "[$-C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"}, {"43543.503206018519", "[$-C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."}, {"43543.503206018519", "[$-C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"}, {"44562.189571759256", "[$-701A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-701A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-701A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-701A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"43543.503206018519", "[$-701A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"}, {"43543.503206018519", "[$-701A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-701A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-701A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"}, {"44562.189571759256", "[$-7C1A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-7C1A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-7C1A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-7C1A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"43543.503206018519", "[$-7C1A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"}, {"43543.503206018519", "[$-7C1A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-7C1A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-7C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"}, {"44562.189571759256", "[$-181A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-181A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-181A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-181A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"}, {"43543.503206018519", "[$-181A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"}, {"43543.503206018519", "[$-181A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-181A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-181A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"}, {"44562.189571759256", "[$-2C1A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-2C1A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-2C1A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 prije podne"}, {"44562.189571759256", "[$-2C1A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"}, {"43543.503206018519", "[$-2C1A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"}, {"43543.503206018519", "[$-2C1A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-2C1A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-2C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"}, {"44562.189571759256", "[$-241A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-241A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-241A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"}, {"44562.189571759256", "[$-241A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"}, {"43543.503206018519", "[$-241A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"}, {"43543.503206018519", "[$-241A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-241A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"}, {"43543.503206018519", "[$-241A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"}, {"44562.189571759256", "[$-81A]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-81A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-81A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-81A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-81A]mmm dd yyyy h:mm AM/PM", "mart 19 2019 12:04 PM"}, {"43543.503206018519", "[$-81A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto."}, {"43543.503206018519", "[$-81A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto."}, {"43543.503206018519", "[$-81A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"}, {"44562.189571759256", "[$-6C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C]mmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-6C]mmmmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"}, {"43543.503206018519", "[$-6C]mmm dd yyyy h:mm AM/PM", "Matš 19 2019 12:04 PM"}, {"43543.503206018519", "[$-6C]mmmm dd yyyy h:mm AM/PM aaa", "Matšhe 19 2019 12:04 PM Lbb"}, {"43543.503206018519", "[$-6C]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lbb"}, {"43543.503206018519", "[$-6C]mmmmmm dd yyyy h:mm AM/PM dddd", "Matšhe 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-46C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46C]mmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44562.189571759256", "[$-46C]mmmmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"}, {"43543.503206018519", "[$-46C]mmm dd yyyy h:mm AM/PM", "Matš 19 2019 12:04 PM"}, {"43543.503206018519", "[$-46C]mmmm dd yyyy h:mm AM/PM aaa", "Matšhe 19 2019 12:04 PM Lbb"}, {"43543.503206018519", "[$-46C]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lbb"}, {"43543.503206018519", "[$-46C]mmmmmm dd yyyy h:mm AM/PM dddd", "Matšhe 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-32]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-32]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"44562.189571759256", "[$-32]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-32]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"43543.503206018519", "[$-32]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-32]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-32]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-32]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-832]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-832]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"44562.189571759256", "[$-832]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-832]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"43543.503206018519", "[$-832]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-832]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-832]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-832]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-432]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-432]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"44562.189571759256", "[$-432]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-432]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"}, {"43543.503206018519", "[$-432]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-432]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-432]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."}, {"43543.503206018519", "[$-432]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-59]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-59]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-59]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-59]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"43543.503206018519", "[$-59]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-59]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-59]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-59]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"}, {"44562.189571759256", "[$-7C59]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C59]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C59]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C59]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C59]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C59]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-7C59]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-7C59]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"}, {"44562.189571759256", "[$-859]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-859]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"44562.189571759256", "[$-859]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-859]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"}, {"43543.503206018519", "[$-859]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-859]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-859]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"}, {"43543.503206018519", "[$-859]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"}, {"44562.189571759256", "[$-5B]mmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1. 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-5B]mmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-5B]mmmmm dd yyyy h:mm AM/PM", "\u0DA2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-5B]mmmmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"43543.503206018519", "[$-5B]mmm dd yyyy h:mm AM/PM", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4. 19 2019 12:04 \u0DB4.\u0DC0."}, {"43543.503206018519", "[$-5B]mmmm dd yyyy h:mm AM/PM aaa", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"}, {"43543.503206018519", "[$-5B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0DB8 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"}, {"43543.503206018519", "[$-5B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631\u0628\u0639"}, {"44562.189571759256", "[$-45B]mmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1. 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-45B]mmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-45B]mmmmm dd yyyy h:mm AM/PM", "\u0DA2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"44562.189571759256", "[$-45B]mmmmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."}, {"43543.503206018519", "[$-45B]mmm dd yyyy h:mm AM/PM", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4. 19 2019 12:04 \u0DB4.\u0DC0."}, {"43543.503206018519", "[$-45B]mmmm dd yyyy h:mm AM/PM aaa", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"}, {"43543.503206018519", "[$-45B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0DB8 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"}, {"43543.503206018519", "[$-45B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631\u0628\u0639"}, {"44562.189571759256", "[$-1B]mmm dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1B]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1B]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1B]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"}, {"43543.503206018519", "[$-1B]mmm dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"}, {"43543.503206018519", "[$-1B]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 PM ut"}, {"43543.503206018519", "[$-1B]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ut"}, {"43543.503206018519", "[$-1B]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 PM utorok"}, {"44562.189571759256", "[$-41B]mmm dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41B]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41B]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41B]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"}, {"43543.503206018519", "[$-41B]mmm dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"}, {"43543.503206018519", "[$-41B]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 PM ut"}, {"43543.503206018519", "[$-41B]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ut"}, {"43543.503206018519", "[$-41B]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 PM utorok"}, {"44562.189571759256", "[$-24]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 dop."}, {"44562.189571759256", "[$-24]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."}, {"44562.189571759256", "[$-24]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dop."}, {"44562.189571759256", "[$-24]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."}, {"43543.503206018519", "[$-24]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 pop."}, {"43543.503206018519", "[$-24]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 pop. tor."}, {"43543.503206018519", "[$-24]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pop. tor."}, {"43543.503206018519", "[$-24]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 pop. torek"}, {"44562.189571759256", "[$-424]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 dop."}, {"44562.189571759256", "[$-424]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."}, {"44562.189571759256", "[$-424]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dop."}, {"44562.189571759256", "[$-424]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."}, {"43543.503206018519", "[$-424]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 pop."}, {"43543.503206018519", "[$-424]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 pop. tor."}, {"43543.503206018519", "[$-424]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pop. tor."}, {"43543.503206018519", "[$-424]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 pop. torek"}, {"44562.189571759256", "[$-77]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 GH"}, {"44562.189571759256", "[$-77]mmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"}, {"44562.189571759256", "[$-77]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 GH"}, {"44562.189571759256", "[$-77]mmmmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"}, {"43543.503206018519", "[$-77]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 GD"}, {"43543.503206018519", "[$-77]mmmm dd yyyy h:mm AM/PM aaa", "Maarso 19 2019 12:04 GD Tldo"}, {"43543.503206018519", "[$-77]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 GD Tldo"}, {"43543.503206018519", "[$-77]mmmmmm dd yyyy h:mm AM/PM dddd", "Maarso 19 2019 12:04 GD Talaado"}, {"44562.189571759256", "[$-477]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 GH"}, {"44562.189571759256", "[$-477]mmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"}, {"44562.189571759256", "[$-477]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 GH"}, {"44562.189571759256", "[$-477]mmmmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"}, {"43543.503206018519", "[$-477]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 GD"}, {"43543.503206018519", "[$-477]mmmm dd yyyy h:mm AM/PM aaa", "Maarso 19 2019 12:04 GD Tldo"}, {"43543.503206018519", "[$-477]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 GD Tldo"}, {"43543.503206018519", "[$-477]mmmmmm dd yyyy h:mm AM/PM dddd", "Maarso 19 2019 12:04 GD Talaado"}, {"44562.189571759256", "[$-30]mmm dd yyyy h:mm AM/PM", "Phe 01 2022 4:32 AM"}, {"44562.189571759256", "[$-30]mmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"}, {"44562.189571759256", "[$-30]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"}, {"44562.189571759256", "[$-30]mmmmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"}, {"43543.503206018519", "[$-30]mmm dd yyyy h:mm AM/PM", "Ube 19 2019 12:04 PM"}, {"43543.503206018519", "[$-30]mmmm dd yyyy h:mm AM/PM aaa", "Hlakubele 19 2019 12:04 PM Bed"}, {"43543.503206018519", "[$-30]mmmmm dd yyyy h:mm AM/PM ddd", "H 19 2019 12:04 PM Bed"}, {"43543.503206018519", "[$-30]mmmmmm dd yyyy h:mm AM/PM dddd", "Hlakubele 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-430]mmm dd yyyy h:mm AM/PM", "Phe 01 2022 4:32 AM"}, {"44562.189571759256", "[$-430]mmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"}, {"44562.189571759256", "[$-430]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"}, {"44562.189571759256", "[$-430]mmmmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"}, {"43543.503206018519", "[$-430]mmm dd yyyy h:mm AM/PM", "Ube 19 2019 12:04 PM"}, {"43543.503206018519", "[$-430]mmmm dd yyyy h:mm AM/PM aaa", "Hlakubele 19 2019 12:04 PM Bed"}, {"43543.503206018519", "[$-430]mmmmm dd yyyy h:mm AM/PM ddd", "H 19 2019 12:04 PM Bed"}, {"43543.503206018519", "[$-430]mmmmmm dd yyyy h:mm AM/PM dddd", "Hlakubele 19 2019 12:04 PM Labobedi"}, {"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."}, {"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."}, {"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. ma."}, {"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. ma."}, {"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-2C0A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-2C0A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-2C0A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-2C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-2C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-2C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-200A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-200A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-200A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-200A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-200A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-200A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-400A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-400A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-400A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-400A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-400A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-400A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-340A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-340A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-340A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-340A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-340A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-340A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-240A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-240A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-240A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-240A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-240A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-240A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-140A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-140A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-140A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-140A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-140A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-140A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-5C0A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-5C0A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a.m."}, {"44562.189571759256", "[$-5C0A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a.m."}, {"43543.503206018519", "[$-5C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-5C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-5C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p.m. martes"}, {"43543.503206018519", "[$-1C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-1C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-1C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-300A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-300A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-300A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-440A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-440A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-440A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-100A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-100A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-100A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-480A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-480A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-480A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-580A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-580A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p.m. mar."}, {"43543.503206018519", "[$-580A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p.m. martes"}, {"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."}, {"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."}, {"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-4C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-4C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-4C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-180A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-180A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-180A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-3C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-3C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-3C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-280A]mmm dd yyyy h:mm AM/PM", "Ene. 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-280A]mmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-280A]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-280A]mmmmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-280A]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-280A]mmmm dd yyyy h:mm AM/PM aaa", "Marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-280A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-280A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzo 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-500A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-500A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-500A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"}, {"43543.503206018519", "[$-40A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM ma."}, {"43543.503206018519", "[$-40A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM ma."}, {"43543.503206018519", "[$-40A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"}, {"43543.503206018519", "[$-C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM ma."}, {"43543.503206018519", "[$-C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM ma."}, {"43543.503206018519", "[$-C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"}, {"43543.503206018519", "[$-540A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-540A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"}, {"43543.503206018519", "[$-540A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"}, {"44562.189571759256", "[$-380A]mmm dd yyyy h:mm AM/PM", "Ene. 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-380A]mmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-380A]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-380A]mmmmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-380A]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-380A]mmmm dd yyyy h:mm AM/PM aaa", "Marzo 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-380A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 p. m. mar."}, {"43543.503206018519", "[$-380A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzo 19 2019 12:04 p. m. martes"}, {"44562.189571759256", "[$-1D]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-1D]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"}, {"43543.503206018519", "[$-1D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM tis"}, {"43543.503206018519", "[$-1D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tis"}, {"43543.503206018519", "[$-1D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM tisdag"}, {"44562.189571759256", "[$-81D]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 fm"}, {"44562.189571759256", "[$-81D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 fm"}, {"44562.189571759256", "[$-81D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 fm"}, {"44562.189571759256", "[$-81D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 fm"}, {"43543.503206018519", "[$-81D]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 em"}, {"43543.503206018519", "[$-81D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 em tis"}, {"43543.503206018519", "[$-81D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 em tis"}, {"43543.503206018519", "[$-81D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 em tisdag"}, {"44562.189571759256", "[$-41D]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"}, {"43543.503206018519", "[$-41D]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"}, {"43543.503206018519", "[$-41D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM tis"}, {"43543.503206018519", "[$-41D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tis"}, {"43543.503206018519", "[$-41D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM tisdag"}, {"44562.189571759256", "[$-5A]mmm dd yyyy h:mm AM/PM", "\u071F\u0722 \u070F\u0712 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-5A]mmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-5A]mmmmm dd yyyy h:mm AM/PM", "\u071F 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-5A]mmmmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"}, {"43543.503206018519", "[$-5A]mmm dd yyyy h:mm AM/PM", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B"}, {"43543.503206018519", "[$-5A]mmmm dd yyyy h:mm AM/PM aaa", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"}, {"43543.503206018519", "[$-5A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0710 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"}, {"43543.503206018519", "[$-5A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710"}, {"44562.189571759256", "[$-45A]mmm dd yyyy h:mm AM/PM", "\u071F\u0722 \u070F\u0712 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-45A]mmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-45A]mmmmm dd yyyy h:mm AM/PM", "\u071F 01 2022 4:32 \u0729.\u071B"}, {"44562.189571759256", "[$-45A]mmmmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"}, {"43543.503206018519", "[$-45A]mmm dd yyyy h:mm AM/PM", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B"}, {"43543.503206018519", "[$-45A]mmmm dd yyyy h:mm AM/PM aaa", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"}, {"43543.503206018519", "[$-45A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0710 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"}, {"43543.503206018519", "[$-45A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710"}, {"44562.189571759256", "[$-28]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"}, {"44562.189571759256", "[$-28]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-28]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-28]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-28]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-28]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-28]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-28]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"}, {"44562.189571759256", "[$-7C28]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C28]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C28]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C28]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C28]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C28]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-7C28]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-7C28]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"}, {"44562.189571759256", "[$-428]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"}, {"44562.189571759256", "[$-428]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-428]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-428]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-428]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-428]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-428]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"}, {"43543.503206018519", "[$-428]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"}, {"44562.189571759256", "[$-5F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-5F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"43543.503206018519", "[$-5F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"}, {"43543.503206018519", "[$-5F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-5F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-5F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"}, {"44562.189571759256", "[$-7C5F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-7C5F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"43543.503206018519", "[$-7C5F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"}, {"43543.503206018519", "[$-7C5F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-7C5F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-7C5F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"}, {"44562.189571759256", "[$-85F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"}, {"44562.189571759256", "[$-85F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"}, {"43543.503206018519", "[$-85F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"}, {"43543.503206018519", "[$-85F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-85F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"}, {"43543.503206018519", "[$-85F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"}, {"44562.189571759256", "[$-49]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-49]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-49]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-49]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-49]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-49]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"}, {"43543.503206018519", "[$-49]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"}, {"43543.503206018519", "[$-49]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8"}, {"44562.189571759256", "[$-449]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-449]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-449]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-449]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-449]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-449]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"}, {"43543.503206018519", "[$-449]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"}, {"43543.503206018519", "[$-449]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8"}, {"44562.189571759256", "[$-849]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9. 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-849]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-849]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"44562.189571759256", "[$-849]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-849]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD. 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"}, {"43543.503206018519", "[$-849]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD."}, {"43543.503206018519", "[$-849]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD."}, {"43543.503206018519", "[$-849]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"}, {"44562.189571759256", "[$-44]mmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44]mmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44]mmmmm dd yyyy h:mm AM/PM", "\u0433 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44]mmmmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-44]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-44]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448."}, {"43543.503206018519", "[$-44]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0438\u0448."}, {"43543.503206018519", "[$-44]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448\u04D9\u043C\u0431\u0435"}, {"44562.189571759256", "[$-444]mmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-444]mmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"44562.189571759256", "[$-444]mmmmm dd yyyy h:mm AM/PM", "\u0433 01 2022 4:32 AM"}, {"44562.189571759256", "[$-444]mmmmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"}, {"43543.503206018519", "[$-444]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 PM"}, {"43543.503206018519", "[$-444]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448."}, {"43543.503206018519", "[$-444]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0438\u0448."}, {"43543.503206018519", "[$-444]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448\u04D9\u043C\u0431\u0435"}, {"44562.189571759256", "[$-4A]mmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4A]mmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4A]mmmmm dd yyyy h:mm AM/PM", "\u0C1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-4A]mmmmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"}, {"43543.503206018519", "[$-4A]mmm dd yyyy h:mm AM/PM", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM"}, {"43543.503206018519", "[$-4A]mmmm dd yyyy h:mm AM/PM aaa", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"}, {"43543.503206018519", "[$-4A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0C2E 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"}, {"43543.503206018519", "[$-4A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02"}, {"44562.189571759256", "[$-44A]mmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44A]mmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44A]mmmmm dd yyyy h:mm AM/PM", "\u0C1C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-44A]mmmmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"}, {"43543.503206018519", "[$-44A]mmm dd yyyy h:mm AM/PM", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM"}, {"43543.503206018519", "[$-44A]mmmm dd yyyy h:mm AM/PM aaa", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"}, {"43543.503206018519", "[$-44A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0C2E 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"}, {"43543.503206018519", "[$-44A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02"}, {"44562.189571759256", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E18. 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E40.\u0E22. 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E04. 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E22. 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E04. 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E2A.\u0E04. 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E22. 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E15.\u0E04. 01 2022 4:32 AM"}, {"44866.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E22. 01 2022 4:32 AM"}, {"44896.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E18.\u0E04. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44866.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44896.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44562.189571759256", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44593.189571759256", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44621.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44652.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E40 01 2022 4:32 AM"}, {"44682.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E1E 01 2022 4:32 AM"}, {"44713.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44743.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44774.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E2A 01 2022 4:32 AM"}, {"44805.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44835.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM aaa", "\u0E15 01 2022 4:32 AM \u0E2A."}, {"44866.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM ddd", "\u0E1E 01 2022 4:32 AM \u0E2D."}, {"44896.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM dddd", "\u0E18 01 2022 4:32 AM \u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35"}, {"44562.189571759256", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E18. 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E40.\u0E22. 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E04. 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E22. 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E04. 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E2A.\u0E04. 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E22. 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E15.\u0E04. 01 2022 4:32 AM"}, {"44866.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E22. 01 2022 4:32 AM"}, {"44896.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E18.\u0E04. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44866.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19 01 2022 4:32 AM"}, {"44896.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21 01 2022 4:32 AM"}, {"44562.189571759256", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44593.189571759256", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44621.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44652.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E40 01 2022 4:32 AM"}, {"44682.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E1E 01 2022 4:32 AM"}, {"44713.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"}, {"44743.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44774.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E2A 01 2022 4:32 AM"}, {"44805.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"}, {"44835.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM aaa", "\u0E15 01 2022 4:32 AM \u0E2A."}, {"44866.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM ddd", "\u0E1E 01 2022 4:32 AM \u0E2D."}, {"44896.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM dddd", "\u0E18 01 2022 4:32 AM \u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35"}, {"100", "g\"年\"m\"月\"d\"日\";@", "年4月9日"}, {"100", "e\"年\"m\"月\"d\"日\";@", "1900年4月9日"}, {"100", "ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"}, {"100", "[$-411]ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"}, {"43709", "[$-411]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"}, {"43709", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u4EE41年9月1日"}, {"43709", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C1年9月1日"}, {"43709", "[$-411]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"}, {"43709", "[$-411]ggee\"年\"m\"月\"d\"日\";@", "\u4EE401年9月1日"}, {"43709", "[$-411]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C01年9月1日"}, {"43709", "[$-ja-JP-x-gannen]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"}, {"43709", "[$-ja-JP-x-gannen]gge\"年\"m\"月\"d\"日\";@", "\u4EE41年9月1日"}, {"43709", "[$-ja-JP-x-gannen]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C1年9月1日"}, {"43709", "[$-ja-JP-x-gannen]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"}, {"43709", "[$-ja-JP-x-gannen]ggee\"年\"m\"月\"d\"日\";@", "\u4EE401年9月1日"}, {"43709", "[$-ja-JP-x-gannen]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C01年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]gge\"年\"m\"月\"d\"日\";@", "\u4EE4\u5143年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C\u5143年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]ggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u5143年9月1日"}, {"43709", "[$-ja-JP-x-gannen,80]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C\u5143年9月1日"}, {"43466.189571759256", "[$-411]ge\"年\"m\"月\"d\"日\";@", "H31年1月1日"}, {"43466.189571759256", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u5E7331年1月1日"}, {"43466.189571759256", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u5E73\u621031年1月1日"}, {"44896.18957170139", "[$-411]ge\"年\"m\"月\"d\"日\";@", "R4年12月1日"}, {"44896.18957170139", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u4EE44年12月1日"}, {"44896.18957170139", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C4年12月1日"}, {"44562.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44866.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44896.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44562.189571759256", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44866.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44896.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44562.189571759256", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D"}, {"44866.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D"}, {"44896.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D"}, {"44562.189571759256", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44866.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44896.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44562.189571759256", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44866.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44896.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44562.189571759256", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44593.189571759256", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44621.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44652.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44682.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44713.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44743.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44774.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44805.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"}, {"44835.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D"}, {"44866.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D"}, {"44896.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D"}, {"44562.189571759256", "[$-73]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-73]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-73]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-73]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"43543.503206018519", "[$-73]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A"}, {"43543.503206018519", "[$-73]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"}, {"43543.503206018519", "[$-73]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"}, {"43543.503206018519", "[$-73]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1220\u1209\u1235"}, {"44562.189571759256", "[$-873]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"}, {"44562.189571759256", "[$-873]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"}, {"44562.189571759256", "[$-873]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"}, {"44562.189571759256", "[$-873]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"}, {"43543.503206018519", "[$-873]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275"}, {"43543.503206018519", "[$-873]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1230\u1209"}, {"43543.503206018519", "[$-873]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1230\u1209"}, {"43543.503206018519", "[$-873]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1220\u1209\u1235"}, {"44562.189571759256", "[$-473]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-473]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-473]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206"}, {"44562.189571759256", "[$-473]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"}, {"43543.503206018519", "[$-473]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A"}, {"43543.503206018519", "[$-473]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"}, {"43543.503206018519", "[$-473]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"}, {"43543.503206018519", "[$-473]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1220\u1209\u1235"}, {"44562.189571759256", "[$-31]mmm dd yyyy h:mm AM/PM", "Sun 01 2022 4:32 AM"}, {"44562.189571759256", "[$-31]mmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"}, {"44562.189571759256", "[$-31]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"}, {"44562.189571759256", "[$-31]mmmmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"}, {"43543.503206018519", "[$-31]mmm dd yyyy h:mm AM/PM", "Kul 19 2019 12:04 PM"}, {"43543.503206018519", "[$-31]mmmm dd yyyy h:mm AM/PM aaa", "Nyenyankulu 19 2019 12:04 PM Bir"}, {"43543.503206018519", "[$-31]mmmmm dd yyyy h:mm AM/PM ddd", "N 19 2019 12:04 PM Bir"}, {"43543.503206018519", "[$-31]mmmmmm dd yyyy h:mm AM/PM dddd", "Nyenyankulu 19 2019 12:04 PM Ravumbirhi"}, {"44562.189571759256", "[$-431]mmm dd yyyy h:mm AM/PM", "Sun 01 2022 4:32 AM"}, {"44562.189571759256", "[$-431]mmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"}, {"44562.189571759256", "[$-431]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"}, {"44562.189571759256", "[$-431]mmmmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"}, {"43543.503206018519", "[$-431]mmm dd yyyy h:mm AM/PM", "Kul 19 2019 12:04 PM"}, {"43543.503206018519", "[$-431]mmmm dd yyyy h:mm AM/PM aaa", "Nyenyankulu 19 2019 12:04 PM Bir"}, {"43543.503206018519", "[$-431]mmmmm dd yyyy h:mm AM/PM ddd", "N 19 2019 12:04 PM Bir"}, {"43543.503206018519", "[$-431]mmmmmm dd yyyy h:mm AM/PM dddd", "Nyenyankulu 19 2019 12:04 PM Ravumbirhi"}, {"44562.189571759256", "[$-1F]mmm dd yyyy h:mm AM/PM", "Oca 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-1F]mmm dd yyyy h:mm AM/PM", "Şub 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Mar 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Nis 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "May 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Haz 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Tem 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Ağu 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Eyl 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Eki 01 2022 4:32 \u00F6\u00F6"}, {"44866.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Kas 01 2022 4:32 \u00F6\u00F6"}, {"44896.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Ara 01 2022 4:32 \u00F6\u00F6"}, {"44562.189571759256", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ocak 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Şubat 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Mart 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Nisan 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Mayıs 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Haziran 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Temmuz 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ağustos 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Eylül 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ekim 01 2022 4:32 \u00F6\u00F6"}, {"44866.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Kasım 01 2022 4:32 \u00F6\u00F6"}, {"44896.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Aralık 01 2022 4:32 \u00F6\u00F6"}, {"44562.189571759256", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "Ş 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "H 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM aaa", "E 01 2022 4:32 \u00F6\u00F6 Cmt"}, {"44866.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM ddd", "K 01 2022 4:32 \u00F6\u00F6 Sal"}, {"44896.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM dddd", "A 01 2022 4:32 \u00F6\u00F6 Perşembe"}, {"44562.189571759256", "[$-41F]mmm dd yyyy h:mm AM/PM", "Oca 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-41F]mmm dd yyyy h:mm AM/PM", "Şub 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Mar 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Nis 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "May 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Haz 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Tem 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Ağu 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Eyl 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Eki 01 2022 4:32 \u00F6\u00F6"}, {"44866.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Kas 01 2022 4:32 \u00F6\u00F6"}, {"44896.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Ara 01 2022 4:32 \u00F6\u00F6"}, {"44562.189571759256", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ocak 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Şubat 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Mart 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Nisan 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Mayıs 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Haziran 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Temmuz 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ağustos 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Eylül 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ekim 01 2022 4:32 \u00F6\u00F6"}, {"44866.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Kasım 01 2022 4:32 \u00F6\u00F6"}, {"44896.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Aralık 01 2022 4:32 \u00F6\u00F6"}, {"44562.189571759256", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 \u00F6\u00F6"}, {"44593.189571759256", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "Ş 01 2022 4:32 \u00F6\u00F6"}, {"44621.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"}, {"44652.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 \u00F6\u00F6"}, {"44682.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"}, {"44713.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "H 01 2022 4:32 \u00F6\u00F6"}, {"44743.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 \u00F6\u00F6"}, {"44774.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 \u00F6\u00F6"}, {"44805.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 \u00F6\u00F6"}, {"44835.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM aaa", "E 01 2022 4:32 \u00F6\u00F6 Cmt"}, {"44866.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM ddd", "K 01 2022 4:32 \u00F6\u00F6 Sal"}, {"44896.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM dddd", "A 01 2022 4:32 \u00F6\u00F6 Perşembe"}, {"44562.189571759256", "[$-42]mmm dd yyyy h:mm AM/PM", "Ýan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42]mmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42]mmmmm dd yyyy h:mm AM/PM", "Ý 01 2022 4:32 AM"}, {"44562.189571759256", "[$-42]mmmmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-42]mmm dd yyyy h:mm AM/PM", "Mart 19 2019 12:04 PM"}, {"43543.503206018519", "[$-42]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 PM Sb"}, {"43543.503206018519", "[$-42]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sb"}, {"43543.503206018519", "[$-42]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 PM Sişenbe"}, {"44562.189571759256", "[$-442]mmm dd yyyy h:mm AM/PM", "Ýan 01 2022 4:32 AM"}, {"44562.189571759256", "[$-442]mmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"}, {"44562.189571759256", "[$-442]mmmmm dd yyyy h:mm AM/PM", "Ý 01 2022 4:32 AM"}, {"44562.189571759256", "[$-442]mmmmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"}, {"43543.503206018519", "[$-442]mmm dd yyyy h:mm AM/PM", "Mart 19 2019 12:04 PM"}, {"43543.503206018519", "[$-442]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 PM Sb"}, {"43543.503206018519", "[$-442]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sb"}, {"43543.503206018519", "[$-442]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 PM Sişenbe"}, {"44562.189571759256", "[$-22]mmm dd yyyy h:mm AM/PM", "\u0421\u0456\u0447 01 2022 4:32 AM"}, {"44562.189571759256", "[$-22]mmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-22]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"}, {"44562.189571759256", "[$-22]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"}, {"43543.503206018519", "[$-22]mmm dd yyyy h:mm AM/PM", "\u0411\u0435\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-22]mmmm dd yyyy h:mm AM/PM aaa", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-22]mmmmm dd yyyy h:mm AM/PM ddd", "\u0431 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-22]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A"}, {"44562.189571759256", "[$-422]mmm dd yyyy h:mm AM/PM", "\u0421\u0456\u0447 01 2022 4:32 AM"}, {"44562.189571759256", "[$-422]mmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-422]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"}, {"44562.189571759256", "[$-422]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"}, {"43543.503206018519", "[$-422]mmm dd yyyy h:mm AM/PM", "\u0411\u0435\u0440 19 2019 12:04 PM"}, {"43543.503206018519", "[$-422]mmmm dd yyyy h:mm AM/PM aaa", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-422]mmmmm dd yyyy h:mm AM/PM ddd", "\u0431 19 2019 12:04 PM \u0412\u0442"}, {"43543.503206018519", "[$-422]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A"}, {"44562.189571759256", "[$-2E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-2E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-2E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-2E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"}, {"43543.503206018519", "[$-2E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 popołdnju"}, {"43543.503206018519", "[$-2E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 popołdnju wut"}, {"43543.503206018519", "[$-2E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 popołdnju wut"}, {"43543.503206018519", "[$-2E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 popołdnju wutora"}, {"44562.189571759256", "[$-42E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-42E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-42E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dopołdnja"}, {"44562.189571759256", "[$-42E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"}, {"43543.503206018519", "[$-42E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 popołdnju"}, {"43543.503206018519", "[$-42E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 popołdnju wut"}, {"43543.503206018519", "[$-42E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 popołdnju wut"}, {"43543.503206018519", "[$-42E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 popołdnju wutora"}, {"44562.189571759256", "[$-20]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-20]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-20]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-20]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-20]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-20]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-20]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-20]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"44562.189571759256", "[$-820]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"}, {"44562.189571759256", "[$-820]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"}, {"44562.189571759256", "[$-820]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u062F\u0646"}, {"44562.189571759256", "[$-820]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"}, {"43543.503206018519", "[$-820]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A"}, {"43543.503206018519", "[$-820]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-820]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-820]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"}, {"44562.189571759256", "[$-420]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-420]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"44562.189571759256", "[$-420]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"}, {"44562.189571759256", "[$-420]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"}, {"43543.503206018519", "[$-420]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"}, {"43543.503206018519", "[$-420]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-420]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"43543.503206018519", "[$-420]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"}, {"44562.189571759256", "[$-80]mmm dd yyyy h:mm AM/PM", "1-\u0626\u0627\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-80]mmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-80]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-80]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"43543.503206018519", "[$-80]mmm dd yyyy h:mm AM/PM", "3-\u0626\u0627\u064A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646"}, {"43543.503206018519", "[$-80]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"}, {"43543.503206018519", "[$-80]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"}, {"43543.503206018519", "[$-80]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5"}, {"44562.189571759256", "[$-480]mmm dd yyyy h:mm AM/PM", "1-\u0626\u0627\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-480]mmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-480]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"44562.189571759256", "[$-480]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"}, {"43543.503206018519", "[$-480]mmm dd yyyy h:mm AM/PM", "3-\u0626\u0627\u064A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646"}, {"43543.503206018519", "[$-480]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"}, {"43543.503206018519", "[$-480]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"}, {"43543.503206018519", "[$-480]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5"}, {"44562.189571759256", "[$-7843]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-7843]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-7843]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-7843]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"}, {"43543.503206018519", "[$-7843]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 \u0422\u041A"}, {"43543.503206018519", "[$-7843]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"}, {"43543.503206018519", "[$-7843]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"}, {"43543.503206018519", "[$-7843]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448\u0430\u043D\u0431\u0430"}, {"44562.189571759256", "[$-843]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-843]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-843]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 \u0422\u041E"}, {"44562.189571759256", "[$-843]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"}, {"43543.503206018519", "[$-843]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 \u0422\u041A"}, {"43543.503206018519", "[$-843]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"}, {"43543.503206018519", "[$-843]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"}, {"43543.503206018519", "[$-843]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448\u0430\u043D\u0431\u0430"}, {"44562.189571759256", "[$-43]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"}, {"44562.189571759256", "[$-43]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"44562.189571759256", "[$-43]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"}, {"44562.189571759256", "[$-43]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"43543.503206018519", "[$-43]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"}, {"43543.503206018519", "[$-43]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-43]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-43]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"}, {"44562.189571759256", "[$-7C43]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"}, {"44562.189571759256", "[$-7C43]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"44562.189571759256", "[$-7C43]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"}, {"44562.189571759256", "[$-7C43]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"43543.503206018519", "[$-7C43]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"}, {"43543.503206018519", "[$-7C43]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-7C43]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-7C43]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"}, {"44562.189571759256", "[$-443]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"}, {"44562.189571759256", "[$-443]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"44562.189571759256", "[$-443]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"}, {"44562.189571759256", "[$-443]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"}, {"43543.503206018519", "[$-443]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"}, {"43543.503206018519", "[$-443]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-443]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"}, {"43543.503206018519", "[$-443]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"}, {"44562.189571759256", "[$-803]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-803]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-803]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."}, {"44562.189571759256", "[$-803]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."}, {"43543.503206018519", "[$-803]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."}, {"43543.503206018519", "[$-803]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-803]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."}, {"43543.503206018519", "[$-803]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"}, {"44562.189571759256", "[$-33]mmm dd yyyy h:mm AM/PM", "Pha 01 2022 4:32 AM"}, {"44562.189571759256", "[$-33]mmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"}, {"44562.189571759256", "[$-33]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"}, {"44562.189571759256", "[$-33]mmmmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"}, {"43543.503206018519", "[$-33]mmm dd yyyy h:mm AM/PM", "Ṱhf 19 2019 12:04 PM"}, {"43543.503206018519", "[$-33]mmmm dd yyyy h:mm AM/PM aaa", "Ṱhafamuhwe 19 2019 12:04 PM Vhi"}, {"43543.503206018519", "[$-33]mmmmm dd yyyy h:mm AM/PM ddd", "Ṱ 19 2019 12:04 PM Vhi"}, {"43543.503206018519", "[$-33]mmmmmm dd yyyy h:mm AM/PM dddd", "Ṱhafamuhwe 19 2019 12:04 PM Ḽavhuvhili"}, {"44562.189571759256", "[$-433]mmm dd yyyy h:mm AM/PM", "Pha 01 2022 4:32 AM"}, {"44562.189571759256", "[$-433]mmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"}, {"44562.189571759256", "[$-433]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"}, {"44562.189571759256", "[$-433]mmmmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"}, {"43543.503206018519", "[$-433]mmm dd yyyy h:mm AM/PM", "Ṱhf 19 2019 12:04 PM"}, {"43543.503206018519", "[$-433]mmmm dd yyyy h:mm AM/PM aaa", "Ṱhafamuhwe 19 2019 12:04 PM Vhi"}, {"43543.503206018519", "[$-433]mmmmm dd yyyy h:mm AM/PM ddd", "Ṱ 19 2019 12:04 PM Vhi"}, {"43543.503206018519", "[$-433]mmmmmm dd yyyy h:mm AM/PM dddd", "Ṱhafamuhwe 19 2019 12:04 PM Ḽavhuvhili"}, {"44562.189571759256", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 10 01 2022 4:32 SA"}, {"44866.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 11 01 2022 4:32 SA"}, {"44896.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 12 01 2022 4:32 SA"}, {"44562.189571759256", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 10 01 2022 4:32 SA"}, {"44866.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 11 01 2022 4:32 SA"}, {"44896.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 12 01 2022 4:32 SA"}, {"44562.189571759256", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM aaa", "T 10 01 2022 4:32 SA T7"}, {"44866.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM ddd", "T 11 01 2022 4:32 SA T3"}, {"44896.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM dddd", "T 12 01 2022 4:32 SA Th\u01B0\u0301 N\u0103m"}, {"44562.189571759256", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 10 01 2022 4:32 SA"}, {"44866.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 11 01 2022 4:32 SA"}, {"44896.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 12 01 2022 4:32 SA"}, {"44562.189571759256", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 10 01 2022 4:32 SA"}, {"44866.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 11 01 2022 4:32 SA"}, {"44896.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 12 01 2022 4:32 SA"}, {"44562.189571759256", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 1 01 2022 4:32 SA"}, {"44593.189571759256", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 2 01 2022 4:32 SA"}, {"44621.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 3 01 2022 4:32 SA"}, {"44652.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 4 01 2022 4:32 SA"}, {"44682.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 5 01 2022 4:32 SA"}, {"44713.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 6 01 2022 4:32 SA"}, {"44743.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 7 01 2022 4:32 SA"}, {"44774.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 8 01 2022 4:32 SA"}, {"44805.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 9 01 2022 4:32 SA"}, {"44835.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM aaa", "T 10 01 2022 4:32 SA T7"}, {"44866.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM ddd", "T 11 01 2022 4:32 SA T3"}, {"44896.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM dddd", "T 12 01 2022 4:32 SA Th\u01B0\u0301 N\u0103m"}, {"44562.189571759256", "[$-52]mmm dd yyyy h:mm AM/PM", "Ion 01 2022 4:32 yb"}, {"44593.189571759256", "[$-52]mmm dd yyyy h:mm AM/PM", "Chwef 01 2022 4:32 yb"}, {"44621.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Maw 01 2022 4:32 yb"}, {"44652.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Ebr 01 2022 4:32 yb"}, {"44682.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"}, {"44713.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Meh 01 2022 4:32 yb"}, {"44743.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Gorff 01 2022 4:32 yb"}, {"44774.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"}, {"44805.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"}, {"44835.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Hyd 01 2022 4:32 yb"}, {"44866.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Tach 01 2022 4:32 yb"}, {"44896.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Rhag 01 2022 4:32 yb"}, {"44562.189571759256", "[$-52]mmmm dd yyyy h:mm AM/PM", "Ionawr 01 2022 4:32 yb"}, {"44593.189571759256", "[$-52]mmmm dd yyyy h:mm AM/PM", "Chwefror 01 2022 4:32 yb"}, {"44621.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mawrth 01 2022 4:32 yb"}, {"44652.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Ebrill 01 2022 4:32 yb"}, {"44682.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"}, {"44713.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mehefin 01 2022 4:32 yb"}, {"44743.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Gorffennaf 01 2022 4:32 yb"}, {"44774.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"}, {"44805.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"}, {"44835.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Hydref 01 2022 4:32 yb"}, {"44866.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Tachwedd 01 2022 4:32 yb"}, {"44896.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Rhagfyr 01 2022 4:32 yb"}, {"44562.189571759256", "[$-52]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 yb"}, {"44593.189571759256", "[$-52]mmmmm dd yyyy h:mm AM/PM", "C 01 2022 4:32 yb"}, {"44621.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44652.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 yb"}, {"44682.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44713.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44743.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 yb"}, {"44774.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 yb"}, {"44805.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44835.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM aaa", "H 01 2022 4:32 yb Sad"}, {"44866.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM ddd", "T 01 2022 4:32 yb Maw"}, {"44896.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM dddd", "R 01 2022 4:32 yb Dydd Iau"}, {"44562.189571759256", "[$-452]mmm dd yyyy h:mm AM/PM", "Ion 01 2022 4:32 yb"}, {"44593.189571759256", "[$-452]mmm dd yyyy h:mm AM/PM", "Chwef 01 2022 4:32 yb"}, {"44621.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Maw 01 2022 4:32 yb"}, {"44652.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Ebr 01 2022 4:32 yb"}, {"44682.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"}, {"44713.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Meh 01 2022 4:32 yb"}, {"44743.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Gorff 01 2022 4:32 yb"}, {"44774.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"}, {"44805.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"}, {"44835.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Hyd 01 2022 4:32 yb"}, {"44866.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Tach 01 2022 4:32 yb"}, {"44896.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Rhag 01 2022 4:32 yb"}, {"44562.189571759256", "[$-452]mmmm dd yyyy h:mm AM/PM", "Ionawr 01 2022 4:32 yb"}, {"44593.189571759256", "[$-452]mmmm dd yyyy h:mm AM/PM", "Chwefror 01 2022 4:32 yb"}, {"44621.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mawrth 01 2022 4:32 yb"}, {"44652.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Ebrill 01 2022 4:32 yb"}, {"44682.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"}, {"44713.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mehefin 01 2022 4:32 yb"}, {"44743.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Gorffennaf 01 2022 4:32 yb"}, {"44774.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"}, {"44805.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"}, {"44835.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Hydref 01 2022 4:32 yb"}, {"44866.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Tachwedd 01 2022 4:32 yb"}, {"44896.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Rhagfyr 01 2022 4:32 yb"}, {"44562.189571759256", "[$-452]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 yb"}, {"44593.189571759256", "[$-452]mmmmm dd yyyy h:mm AM/PM", "C 01 2022 4:32 yb"}, {"44621.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44652.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 yb"}, {"44682.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44713.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44743.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 yb"}, {"44774.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 yb"}, {"44805.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"}, {"44835.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM aaa", "H 01 2022 4:32 yb Sad"}, {"44866.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM ddd", "T 01 2022 4:32 yb Maw"}, {"44896.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM dddd", "R 01 2022 4:32 yb Dydd Iau"}, {"44562.189571759256", "[$-88]mmm dd yyyy h:mm AM/PM", "Sam. 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-88]mmm dd yyyy h:mm AM/PM", "Few. 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Maa 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Awr. 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Suw 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Sul. 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Sept. 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Okt. 01 2022 4:32 Sub"}, {"44866.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Now. 01 2022 4:32 Sub"}, {"44896.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Des. 01 2022 4:32 Sub"}, {"44562.189571759256", "[$-88]mmmm dd yyyy h:mm AM/PM", "Samwiye 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-88]mmmm dd yyyy h:mm AM/PM", "Fewriye 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Maars 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Awril 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Suwe 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Sullet 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Septàmbar 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Oktoobar 01 2022 4:32 Sub"}, {"44866.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Noowàmbar 01 2022 4:32 Sub"}, {"44896.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Desàmbar 01 2022 4:32 Sub"}, {"44562.189571759256", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-88]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "U 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 Sub Gaa."}, {"44866.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 Sub Tal."}, {"44896.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 Sub Alxames"}, {"44562.189571759256", "[$-488]mmm dd yyyy h:mm AM/PM", "Sam. 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-488]mmm dd yyyy h:mm AM/PM", "Few. 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Maa 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Awr. 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Suw 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Sul. 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Sept. 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Okt. 01 2022 4:32 Sub"}, {"44866.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Now. 01 2022 4:32 Sub"}, {"44896.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Des. 01 2022 4:32 Sub"}, {"44562.189571759256", "[$-488]mmmm dd yyyy h:mm AM/PM", "Samwiye 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-488]mmmm dd yyyy h:mm AM/PM", "Fewriye 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Maars 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Awril 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Suwe 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Sullet 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Septàmbar 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Oktoobar 01 2022 4:32 Sub"}, {"44866.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Noowàmbar 01 2022 4:32 Sub"}, {"44896.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Desàmbar 01 2022 4:32 Sub"}, {"44562.189571759256", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44593.189571759256", "[$-488]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 Sub"}, {"44621.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"}, {"44652.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 Sub"}, {"44682.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"}, {"44713.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44743.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44774.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "U 01 2022 4:32 Sub"}, {"44805.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"}, {"44835.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 Sub Gaa."}, {"44866.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 Sub Tal."}, {"44896.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 Sub Alxames"}, {"44562.189571759256", "[$-34]mmm dd yyyy h:mm AM/PM", "uJan. 01 2022 4:32 AM"}, {"44593.189571759256", "[$-34]mmm dd yyyy h:mm AM/PM", "uFeb. 01 2022 4:32 AM"}, {"44621.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uMat. 01 2022 4:32 AM"}, {"44652.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uEpr. 01 2022 4:32 AM"}, {"44682.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uMey. 01 2022 4:32 AM"}, {"44713.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uJun. 01 2022 4:32 AM"}, {"44743.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uJul. 01 2022 4:32 AM"}, {"44774.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uAg. 01 2022 4:32 AM"}, {"44805.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uSep. 01 2022 4:32 AM"}, {"44835.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uOkt. 01 2022 4:32 AM"}, {"44866.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uNov. 01 2022 4:32 AM"}, {"44896.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uDis. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJanuwari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-34]mmmm dd yyyy h:mm AM/PM", "uFebuwari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uMatshi 01 2022 4:32 AM"}, {"44652.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uAprili 01 2022 4:32 AM"}, {"44682.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uMeyi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJuni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJulayi 01 2022 4:32 AM"}, {"44774.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uAgasti 01 2022 4:32 AM"}, {"44805.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uSeptemba 01 2022 4:32 AM"}, {"44835.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uOktobha 01 2022 4:32 AM"}, {"44866.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uNovemba 01 2022 4:32 AM"}, {"44896.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uDisemba 01 2022 4:32 AM"}, {"44562.189571759256", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44593.189571759256", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44621.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44652.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44682.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44713.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44743.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44774.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44805.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44835.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM aaa", "u 01 2022 4:32 AM uMgq."}, {"44866.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM ddd", "u 01 2022 4:32 AM uLwesib."}, {"44896.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM dddd", "u 01 2022 4:32 AM Lwesine"}, {"44562.189571759256", "[$-434]mmm dd yyyy h:mm AM/PM", "uJan. 01 2022 4:32 AM"}, {"44593.189571759256", "[$-434]mmm dd yyyy h:mm AM/PM", "uFeb. 01 2022 4:32 AM"}, {"44621.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uMat. 01 2022 4:32 AM"}, {"44652.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uEpr. 01 2022 4:32 AM"}, {"44682.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uMey. 01 2022 4:32 AM"}, {"44713.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uJun. 01 2022 4:32 AM"}, {"44743.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uJul. 01 2022 4:32 AM"}, {"44774.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uAg. 01 2022 4:32 AM"}, {"44805.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uSep. 01 2022 4:32 AM"}, {"44835.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uOkt. 01 2022 4:32 AM"}, {"44866.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uNov. 01 2022 4:32 AM"}, {"44896.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uDis. 01 2022 4:32 AM"}, {"44562.189571759256", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJanuwari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-434]mmmm dd yyyy h:mm AM/PM", "uFebuwari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uMatshi 01 2022 4:32 AM"}, {"44652.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uAprili 01 2022 4:32 AM"}, {"44682.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uMeyi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJuni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJulayi 01 2022 4:32 AM"}, {"44774.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uAgasti 01 2022 4:32 AM"}, {"44805.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uSeptemba 01 2022 4:32 AM"}, {"44835.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uOktobha 01 2022 4:32 AM"}, {"44866.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uNovemba 01 2022 4:32 AM"}, {"44896.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uDisemba 01 2022 4:32 AM"}, {"44562.189571759256", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44593.189571759256", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44621.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44652.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44682.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44713.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44743.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44774.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44805.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"}, {"44835.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM aaa", "u 01 2022 4:32 AM uMgq."}, {"44866.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM ddd", "u 01 2022 4:32 AM uLwesib."}, {"44896.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM dddd", "u 01 2022 4:32 AM Lwesine"}, {"44562.189571759256", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44866.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44896.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44562.189571759256", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44866.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44896.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44562.189571759256", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA2CD 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA44D 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA315 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA1D6 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA26C 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA0D8 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA3C3 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA246 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA22C 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM aaa", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA0D8"}, {"44866.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM ddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA44D"}, {"44896.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM dddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA282\uA1D6"}, {"44562.189571759256", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44866.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44896.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44562.189571759256", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44866.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44896.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"}, {"44562.189571759256", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA2CD 01 2022 4:32 \uA3B8\uA111"}, {"44593.189571759256", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA44D 01 2022 4:32 \uA3B8\uA111"}, {"44621.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA315 01 2022 4:32 \uA3B8\uA111"}, {"44652.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA1D6 01 2022 4:32 \uA3B8\uA111"}, {"44682.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA26C 01 2022 4:32 \uA3B8\uA111"}, {"44713.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA0D8 01 2022 4:32 \uA3B8\uA111"}, {"44743.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA3C3 01 2022 4:32 \uA3B8\uA111"}, {"44774.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA246 01 2022 4:32 \uA3B8\uA111"}, {"44805.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA22C 01 2022 4:32 \uA3B8\uA111"}, {"44835.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM aaa", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA0D8"}, {"44866.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM ddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA44D"}, {"44896.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM dddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA282\uA1D6"}, {"44562.189571759256", "[$-43D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"}, {"44562.189571759256", "[$-43D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"}, {"44562.189571759256", "[$-43D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"}, {"44562.189571759256", "[$-43D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"}, {"43543.503206018519", "[$-43D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"}, {"43543.503206018519", "[$-43D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-43D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D9\u05D5\u05DD \u05D2"}, {"43543.503206018519", "[$-43D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D3\u05D9\u05E0\u05E1\u05D8\u05D9\u05E7"}, {"44562.189571759256", "[$-6A]mmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-6A]mmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-6A]mmmmm dd yyyy h:mm AM/PM", "\u1E62 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-6A]mmmmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"43543.503206018519", "[$-6A]mmm dd yyyy h:mm AM/PM", "\u1EB8r 19 2019 12:04 \u1ECC\u0300sán"}, {"43543.503206018519", "[$-6A]mmmm dd yyyy h:mm AM/PM aaa", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"}, {"43543.503206018519", "[$-6A]mmmmm dd yyyy h:mm AM/PM ddd", "\u1EB8 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"}, {"43543.503206018519", "[$-6A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán \u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun"}, {"44562.189571759256", "[$-46A]mmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-46A]mmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-46A]mmmmm dd yyyy h:mm AM/PM", "\u1E62 01 2022 4:32 Àár\u1ECD\u0300"}, {"44562.189571759256", "[$-46A]mmmmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"}, {"43543.503206018519", "[$-46A]mmm dd yyyy h:mm AM/PM", "\u1EB8r 19 2019 12:04 \u1ECC\u0300sán"}, {"43543.503206018519", "[$-46A]mmmm dd yyyy h:mm AM/PM aaa", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"}, {"43543.503206018519", "[$-46A]mmmmm dd yyyy h:mm AM/PM ddd", "\u1EB8 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"}, {"43543.503206018519", "[$-46A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán \u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun"}, {"44562.189571759256", "[$-35]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-35]mmm dd yyyy h:mm AM/PM", "Feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Mas 01 2022 4:32 AM"}, {"44652.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Eph 01 2022 4:32 AM"}, {"44682.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Mey 01 2022 4:32 AM"}, {"44713.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Agas 01 2022 4:32 AM"}, {"44805.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Dis 01 2022 4:32 AM"}, {"44562.189571759256", "[$-35]mmmm dd yyyy h:mm AM/PM", "Januwari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-35]mmmm dd yyyy h:mm AM/PM", "Febhuwari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Mashi 01 2022 4:32 AM"}, {"44652.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Ephreli 01 2022 4:32 AM"}, {"44682.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Meyi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Julayi 01 2022 4:32 AM"}, {"44774.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Agasti 01 2022 4:32 AM"}, {"44805.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Septemba 01 2022 4:32 AM"}, {"44835.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Okthoba 01 2022 4:32 AM"}, {"44866.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Novemba 01 2022 4:32 AM"}, {"44896.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Disemba 01 2022 4:32 AM"}, {"44562.189571759256", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44593.189571759256", "[$-35]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"}, {"44621.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44652.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 AM"}, {"44682.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44713.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44743.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44774.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 AM"}, {"44805.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"}, {"44835.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 AM Mgq."}, {"44866.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 AM Bi."}, {"44896.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 AM ULwesine"}, {"44562.189571759256", "[$-435]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"}, {"44593.189571759256", "[$-435]mmm dd yyyy h:mm AM/PM", "Feb 01 2022 4:32 AM"}, {"44621.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Mas 01 2022 4:32 AM"}, {"44652.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Eph 01 2022 4:32 AM"}, {"44682.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Mey 01 2022 4:32 AM"}, {"44713.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Jun 01 2022 4:32 AM"}, {"44743.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Jul 01 2022 4:32 AM"}, {"44774.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Agas 01 2022 4:32 AM"}, {"44805.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Sep 01 2022 4:32 AM"}, {"44835.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Okt 01 2022 4:32 AM"}, {"44866.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Nov 01 2022 4:32 AM"}, {"44896.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Dis 01 2022 4:32 AM"}, {"44562.189571759256", "[$-435]mmmm dd yyyy h:mm AM/PM", "Januwari 01 2022 4:32 AM"}, {"44593.189571759256", "[$-435]mmmm dd yyyy h:mm AM/PM", "Febhuwari 01 2022 4:32 AM"}, {"44621.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Mashi 01 2022 4:32 AM"}, {"44652.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Ephreli 01 2022 4:32 AM"}, {"44682.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Meyi 01 2022 4:32 AM"}, {"44713.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Juni 01 2022 4:32 AM"}, {"44743.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Julayi 01 2022 4:32 AM"}, {"44774.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Agasti 01 2022 4:32 AM"}, {"44805.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Septemba 01 2022 4:32 AM"}, {"44835.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Okthoba 01 2022 4:32 AM"}, {"44866.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Novemba 01 2022 4:32 AM"}, {"44896.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Disemba 01 2022 4:32 AM"}, {"44562.189571759256", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44593.189571759256", "[$-435]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"}, {"44621.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44652.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 AM"}, {"44682.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"}, {"44713.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44743.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"}, {"44774.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 AM"}, {"44805.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"}, {"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 AM Mgq."}, {"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 AM Bi."}, {"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 AM ULwesine"}, {"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "Tuesday, March 19, 2019"}, {"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"}, {"text_", "General", "text_"}, {"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"}, {"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000 "}, {"8.0450685976001E+21", "0_);[Red]\\(0\\)", "8045068597600100000000 "}, {"8.0450685976001E-21", "0_);[Red]\\(0\\)", "0 "}, {"8.04506", "0_);[Red]\\(0\\)", "8 "}, {"-0.0450685976001E+21", "0_);[Red]\\(0\\)", "(45068597600100000000)"}, {"-8.0450685976001E+21", "0_);[Red]\\(0\\)", "(8045068597600100000000)"}, {"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"}, {"-8.04506", "0_);[Red]\\(0\\)", "(8)"}, {"-8.04506", "$#,##0.00_);[Red]($#,##0.00)", "($8.05)"}, {"43543.5448726851", `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`, " $43,543.54 "}, {"1234.5678", "0", "1235"}, {"1234.125", "0.00", "1234.13"}, {"1234.5678", "0.00", "1234.57"}, {"1234.5678", "#,##0", "1,235"}, {"1234.5678", "#,##0.00", "1,234.57"}, {"1234.5678", "0%", "123457%"}, {"1234.5678", "#,##0 ;(#,##0)", "1,235 "}, {"1234.5678", "#,##0 ;[red](#,##0)", "1,235 "}, {"1234.5678", "#,##0.00;(#,##0.00)", "1,234.57"}, {"1234.5678", "#,##0.00;[red](#,##0.00)", "1,234.57"}, {"1234.5678", "#", "1235"}, {"1234.5678", "#0", "1235"}, {"1234.5678", "##", "1235"}, {"1234.5678", "00000.00#", "01234.568"}, {"1234.5678", "00000####", "000001235"}, {"1234.5678", "00000######", "000001235"}, {"-1234.5678", "0.00", "-1234.57"}, {"-1234.5678", "0.00;-0.00", "-1234.57"}, {"-1234.5678", "0.00%%", "-12345678.00%%"}, {"2.1", "mmss.0000", "2400.000"}, {"0.007", "[h]:mm:ss.0", "0:10:04.8"}, {"0.007", "[h]:mm:ss.00", "0:10:04.80"}, {"0.007", "[h]:mm:ss.000", "0:10:04.800"}, {"0.007", "[h]:mm:ss.0000", "0:10:04.800"}, {"0.3270833333", "[h]:mm", "7:51"}, {"0.5347222222", "[h]:mm", "12:50"}, {"0.5833333333", "[h]:mm", "14:00"}, {"0.5833333333", "hh", "14"}, {"123", "[h]:mm,:ss.0", "2952:00,:00.0"}, {"123", "yy-.dd", "00-.02"}, {"123", "[DBNum1][$-804]yyyy\"年\"m\"月\";@", "\u4E00\u4E5D\u25CB\u25CB\u5E74\u4E94\u6708"}, {"123", "[DBNum2][$-804]yyyy\"年\"m\"月\";@", "\u58F9\u7396\u96F6\u96F6\u5E74\u4F0D\u6708"}, {"123", "[DBNum3][$-804]yyyy\"年\"m\"月\";@", "\uFF11\uFF19\uFF10\uFF10\u5E74\uFF15\u6708"}, {"1234567890", "[DBNum1][$-804]0.00", "\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D\u25CB.\u25CB\u25CB"}, {"1234567890", "[DBNum2][$-804]0.00", "\u58F9\u8D30\u53C1\u8086\u4F0D\u9646\u67D2\u634C\u7396\u96F6.\u96F6\u96F6"}, {"1234567890", "[DBNum3][$-804]0.00", "\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19\uFF10.\uFF10\uFF10"}, {"1234.5678", "0.00###", "1234.5678"}, {"1234.5678", "00000.00###", "01234.5678"}, {"-1234.5678", "00000.00###;;", ""}, {"1234.5678", "0.00000", "1234.56780"}, {"8.8888666665555487", "0.00000", "8.88887"}, {"8.8888666665555493e+19", "#,000.00", "88,888,666,665,555,500,000.00"}, {"8.8888666665555493e+19", "0.00000", "88888666665555500000.00000"}, {"37947.7500001", "0.00000000E+00", "3.79477500E+04"}, {"2312312321.1231198", "0.00E+00", "2.31E+09"}, {"3.2234623764278598E+33", "0.00E+00", "3.22E+33"}, {"1.234E-16", "0.00000000000000000000", "0.00000000000000012340"}, {"1.234E-16", "0.000000000000000000", "0.000000000000000123"}, {"1.234E-16", "0.000000000000000000%", "0.000000000000012340%"}, {"1.234E-16", "0.000000000000000000%%%%", "0.000000000000012340%"}, {"-123.4567", "# ?/?", "-123 1/2"}, {"123.4567", "# ??/??", "123 37/81"}, {"123.4567", "#\\ ???/???", "123 58/127"}, {"123.4567", "#\\ ?/2", "123 1/2"}, {"123.4567", "#\\ ?/4", "123 2/4"}, {"123.4567", "#\\ ?/8", "123 4/8"}, {"123.4567", "#\\ ?/16", "123 7/16"}, {"123.4567", "#\\ ?/10", "123 5/10"}, {"-123.4567", "#\\ ?/100", "-123 46/100"}, {"123.4567", "#\\ ?/1000", "123 457/1000"}, {"1234.5678", "[$$-409]#,##0.00", "$1,234.57"}, {"123", "[$x.-unknown]#,##0.00", "x.123.00"}, {"123", "[$x.-unknown]MM/DD/YYYY", "x.05/02/1900"}, {"1234.5678", "0.0xxx00", "1234.5xxx68"}, {"80145.899999999994", "[$¥-8004]\" \"#\" \"####\"\"", "¥ 8 0146"}, {"1", "?", "1"}, {"1", "???", " 1"}, {"0", "# ?/?", "0 "}, {"0", "# ??/??", "0 "}, {"0", "# ??/???", "0 "}, {"31.69", "?#???", " 32"}, {"314159.26535", "#,### ??/??", "314,159 13/49"}, {"314159.26535", "# ??? ????/??", "314 159 13/49"}, {"-3.14159265358979323", "# ?/?", "-3 1/7"}, {"-3.14159265358979323", "# ??/??", "-3 1/7 "}, {"-3.14159265358979323", "# ???/???", "-3 16/113"}, {"5.81999", `???? ????`, " 6"}, {"4.6", `# #### ####`, " 5"}, // Unsupported number format {"37947.7500001", "0.00000000E+000", "37947.7500001"}, {"123", "[DBNum4][$-804]yyyy\"年\"m\"月\";@", "123"}, // Invalid number format {"123", "x0.00s", "123"}, {"123", "[h]:m00m:ss", "123"}, {"123", "yy-00dd", "123"}, {"123", "yy-##dd", "123"}, {"123", "xx[h]:mm,:ss.0xx", "xx2952:00,:00.0xx"}, {"-123", "x0.00s", "-123"}, {"-1234.5678", ";E+;", "-1234.5678"}, {"1234.5678", "E+;", "1234.5678"}, {"1234.5678", "00000.00###s", "1234.5678"}, {"-1234.5678", "00000.00###;s;", "-1234.5678"}, } { result := format(item[0], item[1], false, CellTypeNumber, nil) assert.Equal(t, item[2], result, item) } // Test format number with specified date and time format code for _, item := range [][]string{ {"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"}, {"43543.503206018519", "[$-x-sysdate]dddd, mmmm dd, yyyy", "2019年3月19日"}, {"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"}, {"43543.503206018519", "[$-x-systime]h:mm:ss AM/PM", "12:04:37"}, } { result := format(item[0], item[1], false, CellTypeNumber, &Options{ ShortDatePattern: "yyyy/m/d", LongDatePattern: "yyyy\"年\"M\"月\"d\"日\"", LongTimePattern: "H:mm:ss", }) assert.Equal(t, item[2], result, item) } // Test format number with string data type cell value for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} { for _, item := range [][]string{ {"1234.5678", "General", "1234.5678"}, {"1234.5678", "yyyy\"年\"m\"月\"d\"日\";@", "1234.5678"}, {"1234.5678", "h\"时\"mm\"分\"ss\"秒\";@", "1234.5678"}, {"1234.5678", "\"¥\"#,##0.00_);\\(\"¥\"#,##0.00\\)", "1234.5678"}, {"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"}, {"1234.5678", "\"text\"@", "text1234.5678"}, } { result := format(item[0], item[1], false, cellType, nil) assert.Equal(t, item[2], result, item) } } nf := numberFormat{} changeNumFmtCode, err := nf.currencyLanguageHandler(nfp.Token{Parts: []nfp.Part{{}}}) assert.Equal(t, ErrUnsupportedNumberFormat, err) assert.False(t, changeNumFmtCode) } func BenchmarkNumFmtPlaceHolder(b *testing.B) { items := [][]string{ {"1", "?", "1"}, {"1", "???", " 1"}, {"0", "# ?/?", "0 "}, {"0", "# ??/??", "0 "}, {"0", "# ??/???", "0 "}, {"31.69", "?#???", " 32"}, {"314159.26535", "#,### ??/??", "314,159 13/49"}, {"314159.26535", "# ??? ????/??", "314 159 13/49"}, {"-3.14159265358979323", "# ?/?", "-3 1/7"}, {"-3.14159265358979323", "# ??/??", "-3 1/7 "}, {"123.4567", "#\\ ?/10", "123 5/10"}, } b.ReportAllocs() for i := 0; i < b.N; i++ { for _, item := range items { _ = format(item[0], item[1], false, CellTypeNumber, nil) } } } ================================================ FILE: picture.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "image" "io" "math" "os" "path" "path/filepath" "strconv" "strings" ) // PictureInsertType defines the type of the picture has been inserted into the // worksheet. type PictureInsertType byte // Insert picture types. const ( PictureInsertTypePlaceOverCells PictureInsertType = iota PictureInsertTypePlaceInCell PictureInsertTypeIMAGE PictureInsertTypeDISPIMG ) // parseGraphicOptions provides a function to parse the format settings of // the picture with default value. func (opts *GraphicOptions) parseGraphicOptions(defaults *GraphicOptions) (*GraphicOptions, error) { if opts == nil { return defaults, nil } if countUTF16String(opts.AltText) > MaxGraphicAltTextLength { return defaults, ErrMaxGraphicAltTextLength } if countUTF16String(opts.Name) > MaxGraphicNameLength { return defaults, ErrMaxGraphicNameLength } if opts.PrintObject == nil { opts.PrintObject = boolPtr(true) } if opts.Locked == nil { opts.Locked = boolPtr(true) } if opts.ScaleX == 0 { opts.ScaleX = defaultDrawingScale } if opts.ScaleY == 0 { opts.ScaleY = defaultDrawingScale } if opts.Positioning != "" && inStrSlice(supportedPositioning, opts.Positioning, true) == -1 { return defaults, newInvalidOptionalValue("Positioning", opts.Positioning, supportedPositioning) } return opts, nil } // parsePictureOptions provides a function to parse the picture options with // default value. func parsePictureOptions(pic *Picture) (*GraphicOptions, error) { if pic.InsertType != PictureInsertTypePlaceOverCells { return nil, ErrParameterInvalid } return pic.Format.parseGraphicOptions(&GraphicOptions{ PrintObject: boolPtr(true), Locked: boolPtr(true), ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale, }) } // AddPicture provides the method to add picture in a sheet by given picture // format set (such as offset, scale, aspect ratio setting and print settings) // and file path, supported image types: BMP, EMF, EMZ, GIF, ICO, JPEG, JPG, // PNG, SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency-safe. Note // that this function only supports adding pictures placed over the cells // currently, and doesn't support adding pictures placed in cells or creating // the Kingsoft WPS Office embedded image cells. For example: // // package main // // import ( // "fmt" // _ "image/gif" // _ "image/jpeg" // _ "image/png" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // // Insert a picture. // if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil { // fmt.Println(err) // return // } // // Insert a picture scaling in the cell with location hyperlink. // enable := true // if err := f.AddPicture("Sheet1", "D2", "image.png", // &excelize.GraphicOptions{ // ScaleX: 0.5, // ScaleY: 0.5, // Hyperlink: "#Sheet2!D8", // HyperlinkType: "Location", // }, // ); err != nil { // fmt.Println(err) // return // } // // Insert a picture offset in the cell with external hyperlink, printing and positioning support. // if err := f.AddPicture("Sheet1", "H2", "image.gif", // &excelize.GraphicOptions{ // PrintObject: &enable, // LockAspectRatio: false, // OffsetX: 15, // OffsetY: 10, // Hyperlink: "https://github.com/xuri/excelize", // HyperlinkType: "External", // Positioning: "oneCell", // }, // ); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } // // The optional parameter "AltText" is used to add alternative text to a graph // object. // // The optional parameter "PrintObject" indicates whether the graph object is // printed when the worksheet is printed, the default value of that is 'true'. // // The optional parameter "Locked" indicates whether lock the graph object. // Locking an object has no effect unless the sheet is protected. // // The optional parameter "LockAspectRatio" indicates whether lock aspect ratio // for the graph object, the default value of that is 'false'. // // The optional parameter "AutoFit" specifies if you make graph object size // auto-fits the cell, the default value of that is 'false'. // // The optional parameter "AutoFitIgnoreAspect" specifies if fill the cell with // the image and ignore its aspect ratio, the default value of that is 'false'. // This option only works when the "AutoFit" is enabled. // // The optional parameter "OffsetX" specifies the horizontal offset of the graph // object with the cell, the default value of that is 0. // // The optional parameter "OffsetY" specifies the vertical offset of the graph // object with the cell, the default value of that is 0. // // The optional parameter "ScaleX" specifies the horizontal scale of graph // object. The value of ScaleX must be a floating-point number greater than 0 // with a precision of two decimal places. The default value of that is 1.0 // which presents 100%. // // The optional parameter "ScaleY" specifies the vertical scale of graph object. // The value of ScaleY must be a floating-point number greater than 0 with a // precision of two decimal places. The default value of that is 1.0 which // presents 100%. // // The optional parameter "Hyperlink" specifies the hyperlink of the graph // object. // // The optional parameter "HyperlinkType" defines two types of // hyperlink "External" for website or "Location" for moving to one of the // cells in this workbook. When the "HyperlinkType" is "Location", // coordinates need to start with "#". // // The optional parameter "Positioning" defines 3 types of the position of a // graph object in a spreadsheet: "oneCell" (Move but don't size with // cells), "twoCell" (Move and size with cells), and "absolute" (Don't move or // size with cells). If you don't set this parameter, the default positioning // is to move and size with cells. func (f *File) AddPicture(sheet, cell, name string, opts *GraphicOptions) error { var err error // Check picture exists first. if _, err = os.Stat(name); os.IsNotExist(err) { return err } ext, ok := supportedImageTypes[strings.ToLower(path.Ext(name))] if !ok { return ErrImgExt } file, _ := os.ReadFile(filepath.Clean(name)) return f.AddPictureFromBytes(sheet, cell, &Picture{Extension: ext, File: file, Format: opts}) } // AddPictureFromBytes provides the method to add picture in a sheet by given // picture format set (such as offset, scale, aspect ratio setting and print // settings), file base name, extension name and file bytes, supported image // types: EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. Note // that this function only supports adding pictures placed over the cells // currently, and doesn't support adding pictures placed in cells or creating // the Kingsoft WPS Office embedded image cells. For example: // // package main // // import ( // "fmt" // _ "image/jpeg" // "os" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // file, err := os.ReadFile("image.jpg") // if err != nil { // fmt.Println(err) // return // } // if err := f.AddPictureFromBytes("Sheet1", "A2", &excelize.Picture{ // Extension: ".jpg", // File: file, // Format: &excelize.GraphicOptions{AltText: "Excel Logo"}, // }); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error { var drawingHyperlinkRID int var hyperlinkType string ext, ok := supportedImageTypes[strings.ToLower(pic.Extension)] if !ok { return ErrImgExt } options, err := parsePictureOptions(pic) if err != nil { return err } img, _, err := image.DecodeConfig(bytes.NewReader(pic.File)) if err != nil { return err } // Read sheet data f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. drawingID := f.countDrawings() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" mediaStr := ".." + strings.TrimPrefix(f.addMedia(pic.File, ext), "xl") var drawingRID int if rels, _ := f.relsReader(drawingRels); rels != nil { for _, rel := range rels.Relationships { if rel.Type == SourceRelationshipImage && rel.Target == mediaStr { drawingRID, _ = strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) break } } } if drawingRID == 0 { drawingRID = f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType) } // Add picture with hyperlink. if options.Hyperlink != "" && options.HyperlinkType != "" { if options.HyperlinkType == "External" { hyperlinkType = options.HyperlinkType } drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType) } ws.mu.Unlock() err = f.addDrawingPicture(sheet, drawingXML, cell, ext, drawingRID, drawingHyperlinkRID, img, options) if err != nil { return err } if err = f.addContentTypePart(drawingID, "drawings"); err != nil { return err } f.addSheetNameSpace(sheet, SourceRelationship) return err } // addSheetLegacyDrawing provides a function to add legacy drawing element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetLegacyDrawing(sheet string, rID int) { ws, _ := f.workSheetReader(sheet) ws.LegacyDrawing = &xlsxLegacyDrawing{ RID: "rId" + strconv.Itoa(rID), } } // addSheetLegacyDrawingHF provides a function to add legacy drawing // header/footer element to xl/worksheets/sheet%d.xml by given // worksheet name and relationship index. func (f *File) addSheetLegacyDrawingHF(sheet string, rID int) { ws, _ := f.workSheetReader(sheet) ws.LegacyDrawingHF = &xlsxLegacyDrawingHF{ RID: "rId" + strconv.Itoa(rID), } } // addSheetDrawing provides a function to add drawing element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetDrawing(sheet string, rID int) { ws, _ := f.workSheetReader(sheet) ws.Drawing = &xlsxDrawing{ RID: "rId" + strconv.Itoa(rID), } } // addSheetPicture provides a function to add picture element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetPicture(sheet string, rID int) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.Picture = &xlsxPicture{ RID: "rId" + strconv.Itoa(rID), } return err } // countDrawings provides a function to get drawing files count storage in the // folder xl/drawings. func (f *File) countDrawings() int { drawings := map[string]struct{}{} f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/drawings/drawing") { drawings[k.(string)] = struct{}{} } return true }) f.Drawings.Range(func(rel, value interface{}) bool { if strings.Contains(rel.(string), "xl/drawings/drawing") { drawings[rel.(string)] = struct{}{} } return true }) return len(drawings) } // addDrawingPicture provides a function to add picture by given sheet, // drawingXML, cell, file name, width, height relationship index and format // sets. func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyperlinkRID int, img image.Config, opts *GraphicOptions) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } if opts.Positioning != "" && inStrSlice(supportedPositioning, opts.Positioning, true) == -1 { return newInvalidOptionalValue("Positioning", opts.Positioning, supportedPositioning) } width, height := img.Width, img.Height if opts.AutoFit { if width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts); err != nil { return err } } else { width = int(float64(width) * opts.ScaleX) height = int(float64(height) * opts.ScaleY) } colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err } cellAnchor := xdrCellAnchor{} from := xlsxFrom{} from.Col = colStart from.ColOff = x1 * EMU from.Row = rowStart from.RowOff = y1 * EMU cellAnchor.From = &from if opts.Positioning != "oneCell" { to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU to.Row = rowEnd to.RowOff = y2 * EMU cellAnchor.To = &to cellAnchor.EditAs = opts.Positioning } pic := xlsxPic{} pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio pic.NvPicPr.CNvPr.ID = cNvPrID pic.NvPicPr.CNvPr.Descr = opts.AltText pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID) if len(opts.Name) > 0 { pic.NvPicPr.CNvPr.Name = opts.Name } if hyperlinkRID != 0 { pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{ R: SourceRelationship.Value, RID: "rId" + strconv.Itoa(hyperlinkRID), } } pic.BlipFill.Blip.R = SourceRelationship.Value pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID) if ext == ".svg" { pic.BlipFill.Blip.ExtList = &xlsxEGOfficeArtExtensionList{ Ext: []xlsxCTOfficeArtExtension{ { URI: ExtURISVG, SVGBlip: xlsxCTSVGBlip{ XMLNSaAVG: NameSpaceDrawing2016SVG.Value, Embed: pic.BlipFill.Blip.Embed, }, }, }, } } pic.SpPr.PrstGeom.Prst = "rect" pic.SpPr.Xfrm.Ext.Cx = width * EMU pic.SpPr.Xfrm.Ext.Cy = height * EMU if opts.Positioning == "oneCell" { cx := x2 * EMU cy := y2 * EMU cellAnchor.Ext = &xlsxPositiveSize2D{ Cx: cx, Cy: cy, } pic.SpPr.Xfrm.Ext.Cx = cx pic.SpPr.Xfrm.Ext.Cy = cy } cellAnchor.Pic = &pic cellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Locked, FPrintsWithSheet: *opts.PrintObject, } content.mu.Lock() defer content.mu.Unlock() if opts.Positioning == "oneCell" { content.OneCellAnchor = append(content.OneCellAnchor, &cellAnchor) } else { content.TwoCellAnchor = append(content.TwoCellAnchor, &cellAnchor) } f.Drawings.Store(drawingXML, content) return err } // countMedia provides a function to get media files count storage in the // folder xl/media/image. func (f *File) countMedia() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/media/image") { count++ } return true }) return count } // addMedia provides a function to add a picture into folder xl/media/image by // given file and extension name. Duplicate images are only actually stored once // and drawings that use it will reference the same image. func (f *File) addMedia(file []byte, ext string) string { count := f.countMedia() var name string f.Pkg.Range(func(k, existing interface{}) bool { if !strings.HasPrefix(k.(string), "xl/media/image") { return true } if bytes.Equal(file, existing.([]byte)) { name = k.(string) return false } return true }) if name != "" { return name } media := "xl/media/image" + strconv.Itoa(count+1) + ext f.Pkg.Store(media, file) return media } // GetPictures provides a function to get picture meta info and raw content // embed in spreadsheet by given worksheet and cell name. This function // returns the image contents as []byte data types. This function is // concurrency safe. Note that this function currently does not support // retrieving all properties from the image's Format property, and the value of // the ScaleX and ScaleY property is a floating-point number greater than 0 with // a precision of two decimal places. For example: // // f, err := excelize.OpenFile("Book1.xlsx") // if err != nil { // fmt.Println(err) // return // } // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // pics, err := f.GetPictures("Sheet1", "A2") // if err != nil { // fmt.Println(err) // } // for idx, pic := range pics { // name := fmt.Sprintf("image%d%s", idx+1, pic.Extension) // if err := os.WriteFile(name, pic.File, 0644); err != nil { // fmt.Println(err) // } // } func (f *File) GetPictures(sheet, cell string) ([]Picture, error) { col, row, err := CellNameToCoordinates(cell) if err != nil { return nil, err } col-- row-- f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return nil, err } f.mu.Unlock() if ws.Drawing == nil { return f.getCellImages(sheet, cell) } target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/") drawingRelationships := strings.ReplaceAll( strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels") imgs, err := f.getCellImages(sheet, cell) if err != nil { return nil, err } pics, err := f.getPicture(row, col, drawingXML, drawingRelationships) if err != nil { return nil, err } return append(imgs, pics...), err } // GetPictureCells returns all picture cell references in a worksheet by a // specific worksheet name. func (f *File) GetPictureCells(sheet string) ([]string, error) { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return nil, err } f.mu.Unlock() if ws.Drawing == nil { return f.getImageCells(sheet) } target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/") drawingRelationships := strings.ReplaceAll( strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels") embeddedImageCells, err := f.getImageCells(sheet) if err != nil { return nil, err } imageCells, err := f.getPictureCells(drawingXML, drawingRelationships) if err != nil { return nil, err } return append(embeddedImageCells, imageCells...), err } // DeletePicture provides a function to delete all pictures in a cell by given // worksheet name and cell reference. func (f *File) DeletePicture(sheet, cell string) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } col-- row-- ws, err := f.workSheetReader(sheet) if err != nil { return err } if ws.Drawing == nil { return err } drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl") drawingRels := "xl/drawings/_rels/" + filepath.Base(drawingXML) + ".rels" rIDs, err := f.deleteDrawing(col, row, drawingXML, "Pic") if err != nil { return err } for _, rID := range rIDs { rels := f.getDrawingRelationships(drawingRels, rID) if rels == nil { return err } var used bool checkPicRef := func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/drawings/_rels/drawing") { if k.(string) == drawingRels { return true } r, err := f.relsReader(k.(string)) if err != nil { return true } for _, rel := range r.Relationships { if rel.Type == SourceRelationshipImage && filepath.Base(rel.Target) == filepath.Base(rels.Target) { used = true } } } return true } f.Relationships.Range(checkPicRef) f.Pkg.Range(checkPicRef) if !used { f.Pkg.Delete(strings.ReplaceAll(rels.Target, "../", "xl/")) } f.deleteDrawingRels(drawingRels, rID) } return err } // getPicture provides a function to get picture base name and raw content // embed in spreadsheet by given coordinates and drawing relationships. func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) { var wsDr *xlsxWsDr if wsDr, _, err = f.drawingParser(drawingXML); err != nil { return } wsDr.mu.Lock() defer wsDr.mu.Unlock() cond := func(from *xlsxFrom) bool { return from.Col == col && from.Row == row } cond2 := func(from *decodeFrom) bool { return from.Col == col && from.Row == row } cb := func(a *xdrCellAnchor, r *xlsxRelationship, drawingRelationships string) { if pic := f.extractPictureFromAnchor(drawingRelationships, a, r); pic != nil { pics = append(pics, *pic) } } cb2 := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) { if pic := f.extractPictureFromDecodeAnchor(drawingRelationships, a, r); pic != nil { pics = append(pics, *pic) } } for _, anchor := range wsDr.TwoCellAnchor { f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2) } for _, anchor := range wsDr.OneCellAnchor { f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2) } return } // extractPictureFromAnchor extracts picture data from a cell anchor and // relationship. func (f *File) extractPictureFromAnchor(drawingRelationships string, a *xdrCellAnchor, r *xlsxRelationship) *Picture { var ( cx, cy int pic *Picture ) if buffer, _ := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); buffer != nil { pic = &Picture{ Extension: filepath.Ext(r.Target), File: buffer.([]byte), Format: &GraphicOptions{ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale}, } if a.ClientData != nil { pic.Format.Locked = &a.ClientData.FLocksWithSheet pic.Format.PrintObject = &a.ClientData.FPrintsWithSheet } if a.To == nil { pic.Format.Positioning = "oneCell" } if a.Pic != nil { cx, cy = a.Pic.SpPr.Xfrm.Ext.Cx, a.Pic.SpPr.Xfrm.Ext.Cy if a.From != nil { pic.Format.OffsetX = int(a.From.ColOff / EMU) pic.Format.OffsetY = int(a.From.RowOff / EMU) } pic.Format.LockAspectRatio = a.Pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr pic.Format.Name = a.Pic.NvPicPr.CNvPr.Name if a.Pic.NvPicPr.CNvPr.HlinkClick != nil { if drawRel := f.getDrawingRelationships(drawingRelationships, a.Pic.NvPicPr.CNvPr.HlinkClick.RID); drawRel != nil { pic.Format.Hyperlink = drawRel.Target pic.Format.HyperlinkType = "Location" if drawRel.TargetMode == "External" { pic.Format.HyperlinkType = "External" } } } } f.calculatePictureScale(pic, cx, cy) } return pic } // calculatePictureScale calculates and sets the scale factors for a picture. func (f *File) calculatePictureScale(pic *Picture, cx, cy int) { imgCfg, _, err := image.DecodeConfig(bytes.NewReader(pic.File)) if err != nil || imgCfg.Width <= 0 || imgCfg.Height <= 0 || cx <= 0 || cy <= 0 { return } pic.Format.ScaleX = math.Round(float64(cx)/float64(EMU)/float64(imgCfg.Width)*100) / 100 pic.Format.ScaleY = math.Round(float64(cy)/float64(EMU)/float64(imgCfg.Height)*100) / 100 } // extractPictureFromDecodeAnchor extracts picture data from a decoded cell // anchor and relationship. func (f *File) extractPictureFromDecodeAnchor(drawingRelationships string, a *decodeCellAnchor, r *xlsxRelationship) *Picture { var ( cx, cy int pic *Picture target string ) if strings.HasPrefix(r.Target, "/") { target = strings.TrimPrefix(r.Target, "/") } else { target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target)) } if buffer, _ := f.Pkg.Load(target); buffer != nil { pic = &Picture{ Extension: filepath.Ext(target), File: buffer.([]byte), Format: &GraphicOptions{ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale}, } if a.ClientData != nil { pic.Format.Locked = &a.ClientData.FLocksWithSheet pic.Format.PrintObject = &a.ClientData.FPrintsWithSheet } if a.To == nil { pic.Format.Positioning = "oneCell" } if a.Pic != nil { cx, cy = a.Pic.SpPr.Xfrm.Ext.Cx, a.Pic.SpPr.Xfrm.Ext.Cy if a.From != nil { pic.Format.OffsetX = int(a.From.ColOff / EMU) pic.Format.OffsetY = int(a.From.RowOff / EMU) } pic.Format.LockAspectRatio = a.Pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr pic.Format.Name = a.Pic.NvPicPr.CNvPr.Name if a.Pic.NvPicPr.CNvPr.HlinkClick != nil { if drawRel := f.getDrawingRelationships(drawingRelationships, a.Pic.NvPicPr.CNvPr.HlinkClick.RID); drawRel != nil { pic.Format.Hyperlink = drawRel.Target pic.Format.HyperlinkType = "Location" if drawRel.TargetMode == "External" { pic.Format.HyperlinkType = "External" } } } } f.calculatePictureScale(pic, cx, cy) } return pic } // extractCellAnchor extract drawing object from cell anchor by giving drawing // cell anchor, drawing relationships part path, conditional and callback // function. func (f *File) extractCellAnchor(anchor *xdrCellAnchor, drawingRelationships string, cond func(from *xlsxFrom) bool, cb func(anchor *xdrCellAnchor, rels *xlsxRelationship, drawingRelationships string), cond2 func(from *decodeFrom) bool, cb2 func(anchor *decodeCellAnchor, rels *xlsxRelationship, drawingRelationships string), ) { var drawRel *xlsxRelationship if anchor.GraphicFrame == "" { if anchor.From != nil && anchor.Pic != nil { if cond(anchor.From) { if drawRel = f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed); drawRel != nil { if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok { cb(anchor, drawRel, drawingRelationships) } } } } return } f.extractDecodeCellAnchor(anchor, drawingRelationships, cond2, cb2) } // extractDecodeCellAnchor extract drawing object from cell anchor by giving // decoded drawing cell anchor, drawing relationships part path, conditional and // callback function. func (f *File) extractDecodeCellAnchor(anchor *xdrCellAnchor, drawingRelationships string, cond func(from *decodeFrom) bool, cb func(anchor *decodeCellAnchor, rels *xlsxRelationship, drawingRelationships string), ) { var ( drawRel *xlsxRelationship deCellAnchor = new(decodeCellAnchor) ) _ = f.xmlNewDecoder(strings.NewReader("" + anchor.GraphicFrame + "")).Decode(&deCellAnchor) if deCellAnchor.From != nil && deCellAnchor.Pic != nil { if cond(deCellAnchor.From) { if drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed); drawRel != nil { if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok { cb(deCellAnchor, drawRel, drawingRelationships) } } } } } // getDrawingRelationships provides a function to get drawing relationships // from xl/drawings/_rels/drawing%s.xml.rels by given file name and // relationship ID. func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship { if drawingRels, _ := f.relsReader(rels); drawingRels != nil { drawingRels.mu.Lock() defer drawingRels.mu.Unlock() for _, v := range drawingRels.Relationships { if v.ID == rID { return &v } } } return nil } // drawingsWriter provides a function to save xl/drawings/drawing%d.xml after // serialize structure. func (f *File) drawingsWriter() { f.Drawings.Range(func(path, d interface{}) bool { if d != nil { v, _ := xml.Marshal(d.(*xlsxWsDr)) f.saveFileList(path.(string), v) } return true }) } // drawingResize calculate the height and width after resizing. func (f *File) drawingResize(sheet, cell string, width, height float64, opts *GraphicOptions) (w, h, c, r int, err error) { var mergeCells []MergeCell mergeCells, err = f.GetMergeCells(sheet) if err != nil { return } var rng []int var inMergeCell bool if c, r, err = CellNameToCoordinates(cell); err != nil { return } cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r) for _, mergeCell := range mergeCells { if inMergeCell { continue } if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err == nil { rng, _ = cellRefsToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis()) _ = sortCoordinates(rng) } } if inMergeCell { cellWidth, cellHeight = 0, 0 c, r = rng[0], rng[1] for col := rng[0]; col <= rng[2]; col++ { cellWidth += f.getColWidth(sheet, col) } for row := rng[1]; row <= rng[3]; row++ { cellHeight += f.getRowHeight(sheet, row) } } if float64(cellWidth) < width || float64(cellHeight) < height { aspWidth := float64(cellWidth) / width aspHeight := float64(cellHeight) / height asp := min(aspWidth, aspHeight) width, height = width*asp, height*asp } if opts.AutoFitIgnoreAspect { width, height = float64(cellWidth), float64(cellHeight) } w, h = int(width*opts.ScaleX), int(height*opts.ScaleY) return } // getPictureCells provides a function to get all picture cell references in a // worksheet by given drawing part path and drawing relationships path. func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]string, error) { var ( cells []string err error wsDr *xlsxWsDr ) if wsDr, _, err = f.drawingParser(drawingXML); err != nil { return cells, err } wsDr.mu.Lock() defer wsDr.mu.Unlock() cond := func(from *xlsxFrom) bool { return true } cond2 := func(from *decodeFrom) bool { return true } cb := func(a *xdrCellAnchor, r *xlsxRelationship, drawingRelationships string) { if _, ok := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); ok { if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 { cells = append(cells, cell) } } } cb2 := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) { var target string if strings.HasPrefix(r.Target, "/") { target = strings.TrimPrefix(r.Target, "/") } else { target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target)) } if _, ok := f.Pkg.Load(target); ok { if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 { cells = append(cells, cell) } } } for _, anchor := range wsDr.TwoCellAnchor { f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2) } for _, anchor := range wsDr.OneCellAnchor { f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2) } return cells, err } // cellImagesReader provides a function to get the pointer to the structure // after deserialization of xl/cellimages.xml. func (f *File) cellImagesReader() (*decodeCellImages, error) { if f.DecodeCellImages == nil { f.DecodeCellImages = new(decodeCellImages) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCellImages)))). Decode(f.DecodeCellImages); err != nil && err != io.EOF { return f.DecodeCellImages, err } } return f.DecodeCellImages, nil } // getImageCells returns all the cell images and the Kingsoft WPS // Office embedded image cells reference by given worksheet name. func (f *File) getImageCells(sheet string) ([]string, error) { var ( err error cells []string ) ws, err := f.workSheetReader(sheet) if err != nil { return cells, err } for _, row := range ws.SheetData.Row { for _, c := range row.C { if c.F != nil && c.F.Content != "" && strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(c.F.Content, "="), "_xlfn."), "DISPIMG") { if _, err = f.CalcCellValue(sheet, c.R); err != nil { return cells, err } cells = append(cells, c.R) } r, err := f.getImageCellRel(&c, &Picture{Format: &GraphicOptions{}}) if err != nil { return cells, err } if r != nil { cells = append(cells, c.R) } } } return cells, err } // getRichDataRichValueRel returns relationship of the cell image by given meta // blocks value. func (f *File) getRichDataRichValueRel(val string) (*xlsxRelationship, error) { var r *xlsxRelationship idx, err := strconv.Atoi(val) if err != nil { return r, err } richValueRel, err := f.richValueRelReader() if err != nil { return r, err } if idx >= len(richValueRel.Rels) { return r, err } rID := richValueRel.Rels[idx].ID if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage { return nil, err } return r, err } // getRichDataWebImagesRel returns relationship of a web image by given meta // blocks value. func (f *File) getRichDataWebImagesRel(val string) (*xlsxRelationship, error) { var r *xlsxRelationship idx, err := strconv.Atoi(val) if err != nil { return r, err } richValueWebImages, err := f.richValueWebImageReader() if err != nil { return r, err } if idx >= len(richValueWebImages.WebImageSrd) { return r, err } rID := richValueWebImages.WebImageSrd[idx].Blip.RID if r = f.getRichValueWebImageRelationships(rID); r != nil && r.Type != SourceRelationshipImage { return nil, err } return r, err } // getImageCellRel returns the cell image relationship. func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error) { var r *xlsxRelationship if c.Vm == nil || c.V != formulaErrorVALUE { return r, nil } metaData, err := f.metadataReader() if err != nil { return r, err } vmd := metaData.ValueMetadata if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 { return r, err } richValueIdx := vmd.Bk[*c.Vm-1].Rc[0].V richValue, err := f.richValueReader() if err != nil { return r, err } if richValueIdx >= len(richValue.Rv) { return r, err } rv := richValue.Rv[richValueIdx] rvStructures, err := f.richValueStructuresReader() if err != nil { return r, err } if rv.S >= len(rvStructures.S) { return r, err } rvStruct := rvStructures.S[rv.S] if len(rvStruct.K) != len(rv.V) { return r, err } if idx := rvStruct.getRichDataValueIdx("Text"); idx != -1 { pic.Format.AltText = rv.V[idx] } if idx := rvStruct.getRichDataValueIdx("_rvRel:LocalImageIdentifier"); idx != -1 { pic.InsertType = PictureInsertTypePlaceInCell return f.getRichDataRichValueRel(rv.V[idx]) } // cell image inserted by IMAGE formula function if idx := rvStruct.getRichDataValueIdx("WebImageIdentifier"); idx != -1 { pic.InsertType = PictureInsertTypeIMAGE return f.getRichDataWebImagesRel(rv.V[idx]) } return r, err } // getRichDataValueIdx provides a function to get the index of rich data value // structure by given name and rich data value. func (s *xlsxRichValueStructure) getRichDataValueIdx(n string) int { for idx, k := range s.K { if k.N == n { return idx } } return -1 } // getCellImages provides a function to get the cell images and // the Kingsoft WPS Office embedded cell images by given worksheet name and cell // reference. func (f *File) getCellImages(sheet, cell string) ([]Picture, error) { pics, err := f.getDispImages(sheet, cell) if err != nil { return pics, err } _, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { pic := Picture{Format: &GraphicOptions{}, InsertType: PictureInsertTypePlaceInCell} r, err := f.getImageCellRel(c, &pic) if err != nil || r == nil { return "", true, err } pic.Extension = filepath.Ext(r.Target) if buffer, _ := f.Pkg.Load(strings.TrimPrefix(strings.ReplaceAll(r.Target, "..", "xl"), "/")); buffer != nil { pic.File = buffer.([]byte) pics = append(pics, pic) } return "", true, nil }) return pics, err } // getDispImages provides a function to get the Kingsoft WPS Office embedded // cell images by given worksheet name and cell reference. func (f *File) getDispImages(sheet, cell string) ([]Picture, error) { formula, err := f.GetCellFormula(sheet, cell) if err != nil { return nil, err } if !strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(formula, "="), "_xlfn."), "DISPIMG") { return nil, err } imgID, err := f.CalcCellValue(sheet, cell) if err != nil { return nil, err } cellImages, err := f.cellImagesReader() if err != nil { return nil, err } rels, err := f.relsReader(defaultXMLPathCellImagesRels) if rels == nil { return nil, err } var pics []Picture for _, cellImg := range cellImages.CellImage { if cellImg.Pic.NvPicPr.CNvPr.Name == imgID { for _, r := range rels.Relationships { if r.ID == cellImg.Pic.BlipFill.Blip.Embed { pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}, InsertType: PictureInsertTypeDISPIMG} if buffer, _ := f.Pkg.Load("xl/" + r.Target); buffer != nil { pic.File = buffer.([]byte) pic.Format.AltText = cellImg.Pic.NvPicPr.CNvPr.Descr pic.Format.Name = cellImg.Pic.NvPicPr.CNvPr.Name pics = append(pics, pic) } } } } } return pics, err } ================================================ FILE: picture_test.go ================================================ package excelize import ( "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "io" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" _ "golang.org/x/image/bmp" _ "golang.org/x/image/tiff" ) func BenchmarkAddPictureFromBytes(b *testing.B) { f := NewFile() imgFile, err := os.ReadFile(filepath.Join("test", "images", "excel.png")) if err != nil { b.Error("unable to load image for benchmark") } b.ResetTimer() for i := 1; i <= b.N; i++ { if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: "Excel"}}); err != nil { b.Error(err) } } } func TestAddPicture(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test add picture to worksheet with offset and location hyperlink opts := []GraphicOptions{ {Name: "Picture 4", Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}, {Name: "Picture 4", OffsetX: 10, OffsetY: 10, ScaleX: 0.5, ScaleY: 0.5, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell"}, {Name: "Picture 5", OffsetX: 10, OffsetY: 10, ScaleX: 0.88, ScaleY: 0.88, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External"}, {Name: "Picture 4", PrintObject: boolPtr(true), Locked: boolPtr(true), OffsetX: 200, ScaleX: 1, ScaleY: 1, Positioning: "oneCell"}, {Name: "Picture 9", PrintObject: boolPtr(true), Locked: boolPtr(true), AltText: "Excel Logo", LockAspectRatio: true, ScaleX: 1, ScaleY: 1}, } assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), &opts[0])) // Test add picture to worksheet with offset, external hyperlink and positioning assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), &opts[1])) assert.NoError(t, f.AddPicture("Sheet1", "H21", filepath.Join("test", "images", "excel.jpg"), &opts[2])) // Test add pictures to single cell with offsets assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "oneCell"})) assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), &opts[3])) assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 400, Positioning: "oneCell"})) assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 600, Positioning: "oneCell"})) file, err := os.ReadFile(filepath.Join("test", "images", "excel.png")) assert.NoError(t, err) // Test add picture to worksheet with autofit assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true})) assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 10, OffsetY: 10, AutoFit: true})) assert.NoError(t, f.AddPicture("Sheet1", "C30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true, AutoFitIgnoreAspect: true})) _, err = f.NewSheet("AddPicture") assert.NoError(t, err) assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30)) assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9")) assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1")) assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true})) assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true})) // Test add picture to worksheet from bytes assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &opts[4]})) // Test add picture to worksheet from bytes with unsupported insert type assert.Equal(t, ErrParameterInvalid, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}, InsertType: PictureInsertTypePlaceInCell})) // Test add picture to worksheet from bytes with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}})) for _, preset := range [][]string{{"Q8", "gif"}, {"Q15", "jpg"}, {"Q22", "tif"}, {"Q28", "bmp"}} { assert.NoError(t, f.AddPicture("Sheet1", preset[0], filepath.Join("test", "images", fmt.Sprintf("excel.%s", preset[1])), nil)) } // Test get one cell anchor pictures from worksheet which added pictures with offset pics, err := f.GetPictures("Sheet2", "K22") assert.NoError(t, err) assert.Len(t, pics, 4) assert.Equal(t, opts[3], *pics[1].Format) // Test get one cell anchor pictures from worksheet which added pictures with offset and scale pics, err = f.GetPictures("Sheet1", "F21") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[1], *pics[0].Format) // Test get two cell anchor pictures from worksheet which added pictures with offset and scale pics, err = f.GetPictures("Sheet1", "H21") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[2], *pics[0].Format) // Test get two cell anchor pictures from worksheet which added pictures with alternative text pics, err = f.GetPictures("Sheet1", "Q1") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[4], *pics[0].Format) // Test get two cell anchor pictures from worksheet which added pictures with location hyperlink pics, err = f.GetPictures("Sheet2", "I9") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[0], *pics[0].Format) // Test write file to given path assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx"))) assert.NoError(t, f.Close()) // Test get pictures after inserting a new picture from a workbook which contains existing pictures f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), nil)) pics, err = f.GetPictures("Sheet1", "A30") assert.NoError(t, err) assert.Len(t, pics, 2) // Test get one cell anchor pictures from worksheet which already contains pictures pics, err = f.GetPictures("Sheet2", "K22") assert.NoError(t, err) assert.Len(t, pics, 4) assert.Equal(t, opts[3], *pics[1].Format) // Test get one cell anchor pictures from worksheet which already contains pictures with offset and scale pics, err = f.GetPictures("Sheet1", "F21") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[1], *pics[0].Format) // Test get two cell anchor pictures from worksheet which already contains pictures pics, err = f.GetPictures("Sheet1", "H21") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[2], *pics[0].Format) // Test get two cell anchor pictures from worksheet which already contains pictures with alternative text pics, err = f.GetPictures("Sheet1", "Q1") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[4], *pics[0].Format) // Test get two cell anchor pictures from worksheet which already contains pictures with location hyperlink pics, err = f.GetPictures("Sheet2", "I9") assert.NoError(t, err) assert.Len(t, pics, 1) assert.Equal(t, opts[0], *pics[0].Format) // Test get picture cells cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"H21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx")) assert.NoError(t, err) path := "xl/drawings/drawing1.xml" f.Drawings.Delete(path) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"H21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells) // Test get picture cells with unsupported charset f.Drawings.Delete(path) f.Pkg.Store(path, MacintoshCyrillicCharset) _, err = f.GetPictureCells("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx")) assert.NoError(t, err) // Test get picture cells with unsupported charset f.Pkg.Store(path, MacintoshCyrillicCharset) _, err = f.GetPictureCells("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test add picture with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}), "XML syntax error on line 1: invalid UTF-8") // Test add picture with invalid sheet name assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), nil), ErrSheetNameInvalid.Error()) } func TestAddPictureErrors(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test add picture to worksheet with invalid file path assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), nil)) // Test add picture to worksheet with unsupported file type assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), nil), ErrImgExt.Error()) assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", &Picture{Extension: "jpg", File: make([]byte, 1), Format: &GraphicOptions{AltText: "Excel Logo"}}), ErrImgExt.Error()) // Test add picture to worksheet with invalid file data assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", &Picture{Extension: ".jpg", File: make([]byte, 1), Format: &GraphicOptions{AltText: "Excel Logo"}}), image.ErrFormat.Error()) // Test add picture to worksheet with name length exceeds the limit assert.EqualError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{Name: strings.Repeat("s", MaxGraphicNameLength+1)}), ErrMaxGraphicNameLength.Error()) // Test add picture to worksheet with alt text length exceeds the limit assert.EqualError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{AltText: strings.Repeat("s", MaxGraphicAltTextLength+1)}), ErrMaxGraphicAltTextLength.Error()) // Test add picture with custom image decoder and encoder decode := func(r io.Reader) (image.Image, error) { return nil, nil } decodeConfig := func(r io.Reader) (image.Config, error) { return image.Config{Height: 100, Width: 90}, nil } for cell, ext := range map[string]string{"Q1": "emf", "Q7": "wmf", "Q13": "emz", "Q19": "wmz"} { image.RegisterFormat(ext, "", decode, decodeConfig) assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", fmt.Sprintf("excel.%s", ext)), nil)) } assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{ScaleX: 2.8})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx"))) assert.NoError(t, f.Close()) } func TestGetPicture(t *testing.T) { f := NewFile() assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) pics, err := f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Len(t, pics[0].File, 4718) assert.Empty(t, pics[0].Format.AltText) assert.Equal(t, "Picture 2", pics[0].Format.Name) assert.Equal(t, PictureInsertTypePlaceOverCells, pics[0].InsertType) f, err = prepareTestBook1() if !assert.NoError(t, err) { t.FailNow() } pics, err = f.GetPictures("Sheet1", "F21") assert.NoError(t, err) if !assert.NotEmpty(t, filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension))) || !assert.NotEmpty(t, pics[0].File) || !assert.NoError(t, os.WriteFile(filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension)), pics[0].File, 0o644)) { t.FailNow() } // Try to get picture from a worksheet with illegal cell reference _, err = f.GetPictures("Sheet1", "A") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) // Try to get picture from a worksheet that doesn't contain any images pics, err = f.GetPictures("Sheet3", "I9") assert.EqualError(t, err, "sheet Sheet3 does not exist") assert.Len(t, pics, 0) // Try to get picture from a cell that doesn't contain an image pics, err = f.GetPictures("Sheet2", "A2") assert.NoError(t, err) assert.Len(t, pics, 0) // Test get picture with invalid sheet name _, err = f.GetPictures("Sheet:1", "A2") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8") f.getDrawingRelationships("", "") f.getSheetRelationshipsTargetByID("", "") f.deleteSheetRelationships("", "") // Try to get picture from a local storage file. assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) assert.NoError(t, err) pics, err = f.GetPictures("Sheet1", "F21") assert.NoError(t, err) if !assert.NotEmpty(t, filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension))) || !assert.NotEmpty(t, pics[0].File) || !assert.NoError(t, os.WriteFile(filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension)), pics[0].File, 0o644)) { t.FailNow() } // Try to get picture from a local storage file that doesn't contain an image pics, err = f.GetPictures("Sheet1", "F22") assert.NoError(t, err) assert.Len(t, pics, 0) assert.NoError(t, f.Close()) // Try to get picture with one cell anchor f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing2.xml", []byte(`10151322`)) pics, err = f.GetPictures("Sheet2", "K16") assert.NoError(t, err) assert.Len(t, pics, 1) // Try to get picture cells with one cell anchor cells, err := f.GetPictureCells("Sheet2") assert.NoError(t, err) assert.Equal(t, []string{"K16"}, cells) // Try to get picture cells with absolute target path in the drawing relationship rels, err := f.relsReader("xl/drawings/_rels/drawing2.xml.rels") assert.NoError(t, err) rels.Relationships[0].Target = "/xl/media/image2.jpeg" cells, err = f.GetPictureCells("Sheet2") assert.NoError(t, err) assert.Equal(t, []string{"K16"}, cells) // Try to get pictures with absolute target path in the drawing relationship pics, err = f.GetPictures("Sheet2", "K16") assert.NoError(t, err) assert.Len(t, pics, 1) assert.NoError(t, f.Close()) // Test get picture from none drawing worksheet f = NewFile() pics, err = f.GetPictures("Sheet1", "F22") assert.NoError(t, err) assert.Len(t, pics, 0) f, err = prepareTestBook1() assert.NoError(t, err) // Test get pictures with unsupported charset path := "xl/drawings/drawing1.xml" f.Drawings.Delete(path) f.Pkg.Store(path, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "F21") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f.Drawings.Delete(path) _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get embedded cell pictures f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "F21", "_xlfn.DISPIMG(\"ID_********************************\",1)")) f.Pkg.Store(defaultXMLPathCellImages, []byte(``)) f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(fmt.Sprintf(``, SourceRelationshipImage))) pics, err = f.GetPictures("Sheet1", "F21") assert.NoError(t, err) assert.Len(t, pics, 2) assert.Equal(t, "CellImage1", pics[0].Format.AltText) assert.Equal(t, PictureInsertTypeDISPIMG, pics[0].InsertType) // Test get embedded cell pictures with invalid formula assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "_xlfn.DISPIMG()")) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments") // Test get embedded cell pictures with unsupported charset f.Relationships.Delete(defaultXMLPathCellImagesRels) f.Pkg.Store(defaultXMLPathCellImagesRels, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "F21") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f.Pkg.Store(defaultXMLPathCellImages, MacintoshCyrillicCharset) f.DecodeCellImages = nil _, err = f.GetPictures("Sheet1", "F21") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestAddDrawingPicture(t *testing.T) { // Test addDrawingPicture with illegal cell reference f := NewFile() opts := &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)} assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, image.Config{}, opts), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test addDrawingPicture with invalid positioning types assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning), f.addDrawingPicture("sheet1", "", "A1", "", 0, 0, image.Config{}, &GraphicOptions{Positioning: "x"})) path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", 0, 0, image.Config{}, opts), "XML syntax error on line 1: invalid UTF-8") } func TestAddPictureFromBytes(t *testing.T) { f := NewFile() imgFile, err := os.ReadFile("logo.png") assert.NoError(t, err, "Unable to load logo for test") assert.NoError(t, f.AddPictureFromBytes("Sheet1", "A1", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: strings.Repeat("\u4e00", MaxGraphicAltTextLength), Name: strings.Repeat("\u4e00", MaxGraphicNameLength)}})) assert.NoError(t, f.AddPictureFromBytes("Sheet1", "A50", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: "logo"}})) imageCount := 0 f.Pkg.Range(func(fileName, v interface{}) bool { if strings.Contains(fileName.(string), "media/image") { imageCount++ } return true }) assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.") assert.EqualError(t, f.AddPictureFromBytes("SheetN", "A1", &Picture{Extension: ".png", File: imgFile}), "sheet SheetN does not exist") // Test add picture from bytes with invalid sheet name assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", "A1", &Picture{Extension: ".png", File: imgFile}), ErrSheetNameInvalid.Error()) // Test add picture from bytes with invalid positioning types assert.Equal(t, f.AddPictureFromBytes("Sheet1", "A1", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{Positioning: "x"}}), newInvalidOptionalValue("Positioning", "x", supportedPositioning)) } func TestDeletePicture(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test delete picture on a worksheet which does not contains any pictures assert.NoError(t, f.DeletePicture("Sheet1", "A1")) // Add same pictures on different worksheets assert.NoError(t, f.AddPicture("Sheet1", "F20", filepath.Join("test", "images", "excel.jpg"), nil)) assert.NoError(t, f.AddPicture("Sheet1", "I20", filepath.Join("test", "images", "excel.jpg"), nil)) assert.NoError(t, f.AddPicture("Sheet2", "F1", filepath.Join("test", "images", "excel.jpg"), nil)) // Test delete picture on a worksheet, the images should be preserved assert.NoError(t, f.DeletePicture("Sheet1", "F20")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx"))) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx")) assert.NoError(t, err) // Test delete same picture on different worksheet, the images should be removed assert.NoError(t, f.DeletePicture("Sheet1", "F20")) assert.NoError(t, f.DeletePicture("Sheet1", "I20")) assert.NoError(t, f.DeletePicture("Sheet2", "F1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture2.xlsx"))) // Test delete picture on not exists worksheet assert.EqualError(t, f.DeletePicture("SheetN", "A1"), "sheet SheetN does not exist") // Test delete picture with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.DeletePicture("Sheet:1", "A1")) // Test delete picture with invalid coordinates assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), f.DeletePicture("Sheet1", "")) assert.NoError(t, f.Close()) // Test delete picture on no chart worksheet assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1")) f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx")) assert.NoError(t, err) // Test delete picture with unsupported charset drawing f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.DeletePicture("Sheet1", "F10"), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx")) assert.NoError(t, err) // Test delete picture with unsupported charset drawing relationships f.Relationships.Delete("xl/drawings/_rels/drawing1.xml.rels") f.Pkg.Store("xl/drawings/_rels/drawing1.xml.rels", MacintoshCyrillicCharset) assert.NoError(t, f.DeletePicture("Sheet2", "F1")) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx")) assert.NoError(t, err) // Test delete picture without drawing relationships f.Relationships.Delete("xl/drawings/_rels/drawing1.xml.rels") f.Pkg.Delete("xl/drawings/_rels/drawing1.xml.rels") assert.NoError(t, f.DeletePicture("Sheet1", "I20")) assert.NoError(t, f.Close()) f = NewFile() assert.NoError(t, err) assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), nil)) assert.NoError(t, f.AddPicture("Sheet1", "G1", filepath.Join("test", "images", "excel.jpg"), nil)) drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml") assert.True(t, ok) // Made two picture reference the same drawing relationship ID drawing.(*xlsxWsDr).TwoCellAnchor[1].Pic.BlipFill.Blip.Embed = "rId1" assert.NoError(t, f.DeletePicture("Sheet1", "A1")) assert.NoError(t, f.Close()) } func TestDrawingResize(t *testing.T) { f := NewFile() // Test calculate drawing resize on not exists worksheet _, _, _, _, err := f.drawingResize("SheetN", "A1", 1, 1, nil) assert.EqualError(t, err, "sheet SheetN does not exist") // Test calculate drawing resize with invalid coordinates _, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil) assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true})) } func TestSetContentTypePartRelsExtensions(t *testing.T) { f := NewFile() f.ContentTypes = &xlsxTypes{} assert.NoError(t, f.setContentTypePartRelsExtensions()) // Test set content type part relationships extensions with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.setContentTypePartRelsExtensions(), "XML syntax error on line 1: invalid UTF-8") } func TestSetContentTypePartImageExtensions(t *testing.T) { f := NewFile() // Test set content type part image extensions with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.setContentTypePartImageExtensions(), "XML syntax error on line 1: invalid UTF-8") } func TestSetContentTypePartVMLExtensions(t *testing.T) { f := NewFile() // Test set content type part VML extensions with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.setContentTypePartVMLExtensions(), "XML syntax error on line 1: invalid UTF-8") } func TestAddContentTypePart(t *testing.T) { f := NewFile() // Test add content type part with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.addContentTypePart(0, "unknown"), "XML syntax error on line 1: invalid UTF-8") } func TestGetPictureCells(t *testing.T) { f := NewFile() // Test get picture cells on a worksheet which not contains any pictures cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Empty(t, cells) // Test get picture cells on not exists worksheet _, err = f.GetPictureCells("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) // Test get embedded picture cells f = NewFile() assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "_xlfn.DISPIMG(\"ID_********************************\",1)")) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"A2", "A1"}, cells) // Test get embedded cell pictures with invalid formula assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "_xlfn.DISPIMG()")) _, err = f.GetPictureCells("Sheet1") assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments") assert.NoError(t, f.Close()) } func TestExtractDecodeCellAnchor(t *testing.T) { f := NewFile() cond := func(a *decodeFrom) bool { return true } cb := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) {} f.extractDecodeCellAnchor(&xdrCellAnchor{GraphicFrame: string(MacintoshCyrillicCharset)}, "", cond, cb) } func TestGetCellImages(t *testing.T) { f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err := f.getCellImages("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get the cell images prepareWorkbook := func() *File { f := NewFile() assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) f.Pkg.Store(defaultXMLMetadata, []byte(``)) f.Pkg.Store(defaultXMLRdRichValue, []byte(`05logo`)) f.Pkg.Store(defaultXMLRdRichValueRel, []byte(``)) f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipImage))) f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``)) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ SheetData: xlsxSheetData{Row: []xlsxRow{ {R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}}, }}, }) return f } f = prepareWorkbook() pics, err := f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 1, len(pics)) assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType) assert.Equal(t, "logo", pics[0].Format.AltText) cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) assert.Equal(t, []string{"A1"}, cells) // Test get the cell images without image relationships parts f.Relationships.Delete(defaultXMLRdRichValueRelRels) f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink))) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images with unsupported charset rich data rich value relationships f.Relationships.Delete(defaultXMLRdRichValueRelRels) f.Pkg.Store(defaultXMLRdRichValueRelRels, MacintoshCyrillicCharset) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images with unsupported charset rich data rich value f.Pkg.Store(defaultXMLRdRichValueRel, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get the image cells without block of metadata records cells, err = f.GetPictureCells("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.Empty(t, cells) // Test get the cell images with rich data rich value relationships f.Pkg.Store(defaultXMLMetadata, []byte(``)) f.Pkg.Store(defaultXMLRdRichValueRel, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images with unsupported charset meta data f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get the cell images without block of metadata records f.Pkg.Store(defaultXMLMetadata, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) f = prepareWorkbook() // Test get the cell images with empty image cell rich value f.Pkg.Store(defaultXMLRdRichValue, []byte(`5logo`)) pics, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax") assert.Empty(t, pics) // Test get the cell images without image cell rich value f.Pkg.Store(defaultXMLRdRichValue, []byte(`01`)) f.Pkg.Delete(defaultXMLRdRichValueStructure) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images with unsupported charset rich value f.Pkg.Store(defaultXMLRdRichValue, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f = prepareWorkbook() // Test get the cell images with invalid rich value index f.Pkg.Store(defaultXMLMetadata, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) f = prepareWorkbook() // Test get the cell images inserted by IMAGE formula function f.Pkg.Store(defaultXMLRdRichValue, []byte(`0100`)) f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``)) f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(`
`)) f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink, SourceRelationshipImage))) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 1, len(pics)) assert.Equal(t, PictureInsertTypeIMAGE, pics[0].InsertType) // Test get the cell images inserted by IMAGE formula function with unsupported charset web images relationships f.Relationships.Delete(defaultXMLRdRichValueWebImageRels) f.Pkg.Store(defaultXMLRdRichValueWebImageRels, MacintoshCyrillicCharset) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images inserted by IMAGE formula function without image part f.Relationships.Delete(defaultXMLRdRichValueWebImageRels) f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink, SourceRelationshipHyperLink))) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images inserted by IMAGE formula function with unsupported charset web images part f.Pkg.Store(defaultXMLRdRichValueWebImage, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get the cell images inserted by IMAGE formula function with empty charset web images part f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images inserted by IMAGE formula function with invalid rich value index f.Pkg.Store(defaultXMLRdRichValue, []byte(`100`)) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax") f = prepareWorkbook() // Test get the cell images inserted by IMAGE formula function with not matched rich value and structure keys f.Pkg.Store(defaultXMLRdRichValue, []byte(`0`)) f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images inserted by IMAGE formula function without _rvRel:WebImageIdentifier in rich value structure f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``)) pics, err = f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Empty(t, pics) // Test get the cell images inserted by IMAGE formula function with unsupported charset rich value structures f.Pkg.Store(defaultXMLRdRichValueStructure, MacintoshCyrillicCharset) _, err = f.GetPictures("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestGetImageCells(t *testing.T) { f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err := f.getImageCells("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } ================================================ FILE: pivotTable.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "path/filepath" "reflect" "strconv" "strings" "golang.org/x/text/cases" "golang.org/x/text/language" ) // PivotTableOptions directly maps the format settings of the pivot table. // // PivotTableStyleName: The built-in pivot table style names // // PivotStyleLight1 - PivotStyleLight28 // PivotStyleMedium1 - PivotStyleMedium28 // PivotStyleDark1 - PivotStyleDark28 type PivotTableOptions struct { pivotTableXML string pivotCacheXML string pivotSheetName string pivotDataRange string namedDataRange bool DataRange string PivotTableRange string Name string Rows []PivotTableField Columns []PivotTableField Data []PivotTableField Filter []PivotTableField RowGrandTotals bool ColGrandTotals bool ShowDrill bool UseAutoFormatting bool PageOverThenDown bool MergeItem bool ClassicLayout bool CompactData bool ShowError bool ShowRowHeaders bool ShowColHeaders bool ShowRowStripes bool ShowColStripes bool ShowLastColumn bool FieldPrintTitles bool ItemPrintTitles bool PivotTableStyleName string } // PivotTableField directly maps the field settings of the pivot table. // // Name specifies the name of the data field. Maximum 255 characters // are allowed in data field name, excess characters will be truncated. // // Subtotal specifies the aggregation function that applies to this data // field. The default value is sum. The possible values for this attribute // are: // // Average // Count // CountNums // Max // Min // Product // StdDev // StdDevp // Sum // Var // Varp // // NumFmt specifies the number format ID of the data field, this filed only // accepts built-in number format ID and does not support custom number format // expression currently. type PivotTableField struct { Compact bool Data string Name string Outline bool ShowAll bool InsertBlankRow bool Subtotal string DefaultSubtotal bool NumFmt int } // AddPivotTable provides the method to add pivot table by given pivot table // options. Note that the same fields can not in Columns, Rows and Filter // fields at the same time. // // For example, create a pivot table on the range reference Sheet1!G2:M34 with // the range reference Sheet1!A1:E31 as the data source, summarize by sum for // sales: // // package main // // import ( // "fmt" // "math/rand" // // "github.com/xuri/excelize/v2" // ) // // func main() { // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // // Create some data in a sheet // month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} // year := []int{2017, 2018, 2019} // types := []string{"Meat", "Dairy", "Beverages", "Produce"} // region := []string{"East", "West", "North", "South"} // f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}) // for row := 2; row < 32; row++ { // f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]) // f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)]) // f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]) // f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)) // f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]) // } // if err := f.AddPivotTable(&excelize.PivotTableOptions{ // DataRange: "Sheet1!A1:E31", // PivotTableRange: "Sheet1!G2:M34", // Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, // Filter: []excelize.PivotTableField{{Data: "Region"}}, // Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}}, // Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}}, // RowGrandTotals: true, // ColGrandTotals: true, // ShowDrill: true, // ShowRowHeaders: true, // ShowColHeaders: true, // ShowLastColumn: true, // }); err != nil { // fmt.Println(err) // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } func (f *File) AddPivotTable(opts *PivotTableOptions) error { // parameter validation _, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opts) if err != nil { return err } f.clearCalcCache() pivotTableID := f.countPivotTables() + 1 pivotCacheID := f.countPivotCache() + 1 sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml" opts.pivotTableXML = strings.ReplaceAll(sheetRelationshipsPivotTableXML, "..", "xl") opts.pivotCacheXML = "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml" if err = f.addPivotCache(opts); err != nil { return err } // workbook pivot cache workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, strings.TrimPrefix(opts.pivotCacheXML, "xl/"), "") cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID) pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels" // rId not used _ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") if err = f.addPivotTable(cacheID, pivotTableID, opts); err != nil { return err } pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels" f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil { return err } return f.addContentTypePart(pivotCacheID, "pivotCache") } // parseFormatPivotTableSet provides a function to validate pivot table // properties. func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet, string, error) { if opts == nil { return nil, "", ErrParameterRequired } pivotTableSheetName, _, err := f.adjustRange(opts.PivotTableRange) if err != nil { return nil, "", newPivotTableRangeError(err.Error()) } if countUTF16String(opts.Name) > MaxFieldLength { return nil, "", ErrNameLength } opts.pivotSheetName = pivotTableSheetName if err = f.getPivotTableDataRange(opts); err != nil { return nil, "", err } dataSheetName, _, err := f.adjustRange(opts.pivotDataRange) if err != nil { return nil, "", newPivotTableDataRangeError(err.Error()) } dataSheet, err := f.workSheetReader(dataSheetName) if err != nil { return dataSheet, "", err } pivotTableSheetPath, ok := f.getSheetXMLPath(pivotTableSheetName) if !ok { return dataSheet, pivotTableSheetPath, ErrSheetNotExist{pivotTableSheetName} } if opts.CompactData && opts.ClassicLayout { return nil, "", ErrPivotTableClassicLayout } var colDataFields, rowDataFields []string for _, f := range opts.Filter { if inPivotTableField(opts.Columns, f.Data) != -1 { colDataFields = append(colDataFields, f.Data) } if inPivotTableField(opts.Rows, f.Data) != -1 { rowDataFields = append(rowDataFields, f.Data) } } if len(colDataFields) > 0 { return dataSheet, pivotTableSheetPath, newPivotTableColFieldsError(colDataFields) } if len(rowDataFields) > 0 { return dataSheet, pivotTableSheetPath, newPivotTableRowFieldsError(rowDataFields) } return dataSheet, pivotTableSheetPath, err } // adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31 func (f *File) adjustRange(rangeStr string) (string, []int, error) { if len(rangeStr) < 1 { return "", []int{}, ErrParameterRequired } rng := strings.Split(rangeStr, "!") if len(rng) != 2 { return "", []int{}, ErrParameterInvalid } trimRng := strings.ReplaceAll(rng[1], "$", "") coordinates, err := rangeRefToCoordinates(trimRng) if err != nil { return rng[0], []int{}, err } x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if x1 == x2 && y1 == y2 { return rng[0], []int{}, ErrParameterInvalid } // Correct the range, such correct C1:B3 to B1:C3. if x2 < x1 { x1, x2 = x2, x1 } if y2 < y1 { y1, y2 = y2, y1 } return rng[0], []int{x1, y1, x2, y2}, nil } // getTableFieldsOrder provides a function to get order list of pivot table // fields. func (f *File) getTableFieldsOrder(opts *PivotTableOptions) ([]string, error) { var order []string if err := f.getPivotTableDataRange(opts); err != nil { return order, err } dataSheet, coordinates, err := f.adjustRange(opts.pivotDataRange) if err != nil { return order, newPivotTableDataRangeError(err.Error()) } for col := coordinates[0]; col <= coordinates[2]; col++ { coordinate, _ := CoordinatesToCellName(col, coordinates[1]) name, err := f.GetCellValue(dataSheet, coordinate) if err != nil { return order, err } if name == "" { return order, ErrParameterInvalid } order = append(order, name) } return order, nil } // addPivotCache provides a function to create a pivot cache by given properties. func (f *File) addPivotCache(opts *PivotTableOptions) error { // validate data range dataSheet, coordinates, err := f.adjustRange(opts.pivotDataRange) if err != nil { return newPivotTableDataRangeError(err.Error()) } order, err := f.getTableFieldsOrder(opts) if err != nil { return newPivotTableDataRangeError(err.Error()) } topLeftCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1]) bottomRightCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3]) pc := xlsxPivotCacheDefinition{ SaveData: false, RefreshOnLoad: true, CreatedVersion: pivotTableVersion, RefreshedVersion: pivotTableRefreshedVersion, MinRefreshableVersion: pivotTableVersion, CacheSource: &xlsxCacheSource{ Type: "worksheet", WorksheetSource: &xlsxWorksheetSource{ Ref: topLeftCell + ":" + bottomRightCell, Sheet: dataSheet, }, }, CacheFields: &xlsxCacheFields{}, } if opts.namedDataRange { pc.CacheSource.WorksheetSource = &xlsxWorksheetSource{Name: opts.DataRange} } for _, name := range order { pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{ Name: name, SharedItems: &xlsxSharedItems{ContainsBlank: true, M: []xlsxMissing{{}}}, }) } pc.CacheFields.Count = len(pc.CacheFields.CacheField) pivotCache, err := xml.Marshal(pc) f.saveFileList(opts.pivotCacheXML, pivotCache) return err } // addPivotTable provides a function to create a pivot table by given pivot // table ID and properties. func (f *File) addPivotTable(cacheID, pivotTableID int, opts *PivotTableOptions) error { // validate pivot table range _, coordinates, err := f.adjustRange(opts.PivotTableRange) if err != nil { return newPivotTableRangeError(err.Error()) } topLeftCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1]) bottomRightCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3]) pivotTableStyle := func() string { if opts.PivotTableStyleName == "" { return "PivotStyleLight16" } return opts.PivotTableStyleName } pt := xlsxPivotTableDefinition{ Name: opts.Name, CacheID: cacheID, RowGrandTotals: &opts.RowGrandTotals, ColGrandTotals: &opts.ColGrandTotals, UpdatedVersion: pivotTableRefreshedVersion, MinRefreshableVersion: pivotTableVersion, ShowDrill: &opts.ShowDrill, UseAutoFormatting: &opts.UseAutoFormatting, PageOverThenDown: &opts.PageOverThenDown, MergeItem: &opts.MergeItem, CreatedVersion: pivotTableVersion, CompactData: &opts.CompactData, GridDropZones: opts.ClassicLayout, ShowError: &opts.ShowError, FieldPrintTitles: opts.FieldPrintTitles, ItemPrintTitles: opts.ItemPrintTitles, DataCaption: "Values", Location: &xlsxLocation{ Ref: topLeftCell + ":" + bottomRightCell, FirstDataCol: 1, FirstDataRow: 1, FirstHeaderRow: 1, }, PivotFields: &xlsxPivotFields{}, RowItems: &xlsxRowItems{ Count: 1, I: []*xlsxI{ { []*xlsxX{{}}, }, }, }, ColItems: &xlsxColItems{ Count: 1, I: []*xlsxI{{}}, }, PivotTableStyleInfo: &xlsxPivotTableStyleInfo{ Name: pivotTableStyle(), ShowRowHeaders: opts.ShowRowHeaders, ShowColHeaders: opts.ShowColHeaders, ShowRowStripes: opts.ShowRowStripes, ShowColStripes: opts.ShowColStripes, ShowLastColumn: opts.ShowLastColumn, }, } if pt.Name == "" { pt.Name = fmt.Sprintf("PivotTable%d", pivotTableID) } // set classic layout if opts.ClassicLayout { pt.Compact, pt.CompactData = boolPtr(false), boolPtr(false) } // pivot fields _ = f.addPivotFields(&pt, opts) // count pivot fields pt.PivotFields.Count = len(pt.PivotFields.PivotField) // data range has been checked _ = f.addPivotRowFields(&pt, opts) _ = f.addPivotColFields(&pt, opts) _ = f.addPivotPageFields(&pt, opts) _ = f.addPivotDataFields(&pt, opts) pivotTable, err := xml.Marshal(pt) f.saveFileList(opts.pivotTableXML, pivotTable) return err } // addPivotRowFields provides a method to add row fields for pivot table by // given pivot table options. func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { // row fields rowFieldsIndex, err := f.getPivotFieldsIndex(opts.Rows, opts) if err != nil { return err } for _, fieldIdx := range rowFieldsIndex { if pt.RowFields == nil { pt.RowFields = &xlsxRowFields{} } pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{ X: fieldIdx, }) } // count row fields if pt.RowFields != nil { pt.RowFields.Count = len(pt.RowFields.Field) } return err } // addPivotPageFields provides a method to add page fields for pivot table by // given pivot table options. func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { // page fields pageFieldsIndex, err := f.getPivotFieldsIndex(opts.Filter, opts) if err != nil { return err } pageFieldsName := f.getPivotTableFieldsName(opts.Filter) for idx, pageField := range pageFieldsIndex { if pt.PageFields == nil { pt.PageFields = &xlsxPageFields{} } pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{ Name: pageFieldsName[idx], Fld: pageField, }) } // count page fields if pt.PageFields != nil { pt.PageFields.Count = len(pt.PageFields.PageField) } return err } // addPivotDataFields provides a method to add data fields for pivot table by // given pivot table options. func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { // data fields dataFieldsIndex, err := f.getPivotFieldsIndex(opts.Data, opts) if err != nil { return err } dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opts.Data) dataFieldsName := f.getPivotTableFieldsName(opts.Data) dataFieldsNumFmtID := f.getPivotTableFieldsNumFmtID(opts.Data) for idx, dataField := range dataFieldsIndex { if pt.DataFields == nil { pt.DataFields = &xlsxDataFields{} } pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{ Name: dataFieldsName[idx], Fld: dataField, Subtotal: dataFieldsSubtotals[idx], NumFmtID: dataFieldsNumFmtID[idx], }) } // count data fields if pt.DataFields != nil { pt.DataFields.Count = len(pt.DataFields.DataField) } return err } // inPivotTableField provides a method to check if an element is present in // pivot table fields list, and return the index of its location, otherwise // return -1. func inPivotTableField(a []PivotTableField, x string) int { for idx, n := range a { if x == n.Data { return idx } } return -1 } // addPivotColFields create pivot column fields by given pivot table // definition and option. func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { if len(opts.Columns) == 0 { if len(opts.Data) <= 1 { return nil } pt.ColFields = &xlsxColFields{} // in order to create pivot table in case there is no input from Columns pt.ColFields.Count = 1 pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ X: -2, }) return nil } pt.ColFields = &xlsxColFields{} // col fields colFieldsIndex, err := f.getPivotFieldsIndex(opts.Columns, opts) if err != nil { return err } for _, fieldIdx := range colFieldsIndex { pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ X: fieldIdx, }) } // in order to create pivot in case there is many Columns and Data if len(opts.Data) > 1 { pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ X: -2, }) } // count col fields pt.ColFields.Count = len(pt.ColFields.Field) return err } // setClassicLayout provides a method to set classic layout for pivot table by // setting Compact and Outline to false. func (fld *xlsxPivotField) setClassicLayout(classicLayout bool) { if classicLayout { fld.Compact, fld.Outline = boolPtr(false), boolPtr(false) } } // addPivotFields create pivot fields based on the column order of the first // row in the data region by given pivot table definition and option. func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { order, err := f.getTableFieldsOrder(opts) if err != nil { return err } x := 0 for _, name := range order { if inPivotTableField(opts.Rows, name) != -1 { rowOptions, ok := f.getPivotTableFieldOptions(name, opts.Rows) var items []*xlsxItem if !ok || !rowOptions.DefaultSubtotal { items = append(items, &xlsxItem{X: &x}) } else { items = append(items, &xlsxItem{T: "default"}) } fld := &xlsxPivotField{ Name: f.getPivotTableFieldName(name, opts.Rows), Axis: "axisRow", DataField: inPivotTableField(opts.Data, name) != -1, Compact: &rowOptions.Compact, Outline: &rowOptions.Outline, ShowAll: rowOptions.ShowAll, InsertBlankRow: rowOptions.InsertBlankRow, DefaultSubtotal: &rowOptions.DefaultSubtotal, Items: &xlsxItems{ Count: len(items), Item: items, }, } fld.setClassicLayout(opts.ClassicLayout) pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld) continue } if inPivotTableField(opts.Filter, name) != -1 { fld := &xlsxPivotField{ Axis: "axisPage", DataField: inPivotTableField(opts.Data, name) != -1, Name: f.getPivotTableFieldName(name, opts.Columns), Items: &xlsxItems{ Count: 1, Item: []*xlsxItem{ {T: "default"}, }, }, } fld.setClassicLayout(opts.ClassicLayout) pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld) continue } if inPivotTableField(opts.Columns, name) != -1 { columnOptions, ok := f.getPivotTableFieldOptions(name, opts.Columns) var items []*xlsxItem if !ok || !columnOptions.DefaultSubtotal { items = append(items, &xlsxItem{X: &x}) } else { items = append(items, &xlsxItem{T: "default"}) } fld := &xlsxPivotField{ Name: f.getPivotTableFieldName(name, opts.Columns), Axis: "axisCol", DataField: inPivotTableField(opts.Data, name) != -1, Compact: &columnOptions.Compact, Outline: &columnOptions.Outline, ShowAll: columnOptions.ShowAll, InsertBlankRow: columnOptions.InsertBlankRow, DefaultSubtotal: &columnOptions.DefaultSubtotal, Items: &xlsxItems{ Count: len(items), Item: items, }, } fld.setClassicLayout(opts.ClassicLayout) pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld) continue } if inPivotTableField(opts.Data, name) != -1 { fld := &xlsxPivotField{ DataField: true, } fld.setClassicLayout(opts.ClassicLayout) pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld) continue } fld := &xlsxPivotField{} fld.setClassicLayout(opts.ClassicLayout) pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld) } return err } // countPivotTables provides a function to get pivot table files count storage // in the folder xl/pivotTables. func (f *File) countPivotTables() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/pivotTables/pivotTable") { count++ } return true }) return count } // countPivotCache provides a function to get pivot table cache definition files // count storage in the folder xl/pivotCache. func (f *File) countPivotCache() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") { count++ } return true }) return count } // getPivotFieldsIndex convert the column of the first row in the data region // to a sequential index by given fields and pivot option. func (f *File) getPivotFieldsIndex(fields []PivotTableField, opts *PivotTableOptions) ([]int, error) { var pivotFieldsIndex []int orders, err := f.getTableFieldsOrder(opts) if err != nil { return pivotFieldsIndex, err } for _, field := range fields { if pos := inStrSlice(orders, field.Data, true); pos != -1 { pivotFieldsIndex = append(pivotFieldsIndex, pos) } } return pivotFieldsIndex, nil } // getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields. func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string { field := make([]string, len(fields)) enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"} inEnums := func(enums []string, val string) string { for _, enum := range enums { if strings.EqualFold(enum, val) { return enum } } return "sum" } for idx, fld := range fields { field[idx] = inEnums(enums, fld.Subtotal) } return field } // getPivotTableFieldsName prepare fields name list by given pivot table // fields. func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string { field := make([]string, len(fields)) for idx, fld := range fields { if countUTF16String(fld.Name) > MaxFieldLength { field[idx] = truncateUTF16Units(fld.Name, MaxFieldLength) continue } field[idx] = fld.Name } return field } // getPivotTableFieldName prepare field name by given pivot table fields. func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string { fieldsName := f.getPivotTableFieldsName(fields) for idx, field := range fields { if field.Data == name { return fieldsName[idx] } } return "" } // getPivotTableFieldsNumFmtID prepare fields number format ID by given pivot // table fields. func (f *File) getPivotTableFieldsNumFmtID(fields []PivotTableField) []int { field := make([]int, len(fields)) for idx, fld := range fields { if _, ok := builtInNumFmt[fld.NumFmt]; ok { field[idx] = fld.NumFmt continue } if (27 <= fld.NumFmt && fld.NumFmt <= 36) || (50 <= fld.NumFmt && fld.NumFmt <= 81) { field[idx] = fld.NumFmt } } return field } // getPivotTableFieldOptions return options for specific field by given field name. func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) { for _, field := range fields { if field.Data == name { options, ok = field, true return } } return } // addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml. func (f *File) addWorkbookPivotCache(RID int) int { wb, _ := f.workbookReader() if wb.PivotCaches == nil { wb.PivotCaches = &xlsxPivotCaches{} } cacheID := 1 for _, pivotCache := range wb.PivotCaches.PivotCache { if pivotCache.CacheID > cacheID { cacheID = pivotCache.CacheID } } cacheID++ wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{ CacheID: cacheID, RID: fmt.Sprintf("rId%d", RID), }) return cacheID } // GetPivotTables returns all pivot table definitions in a worksheet by given // worksheet name. Currently only support get pivot table cache with worksheet // source type, and doesn't support source types: external, consolidation // and scenario. func (f *File) GetPivotTables(sheet string) ([]PivotTableOptions, error) { var pivotTables []PivotTableOptions name, ok := f.getSheetXMLPath(sheet) if !ok { return pivotTables, ErrSheetNotExist{sheet} } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" sheetRels, err := f.relsReader(rels) if err != nil { return pivotTables, err } if sheetRels == nil { sheetRels = &xlsxRelationships{} } for _, v := range sheetRels.Relationships { if v.Type == SourceRelationshipPivotTable { pivotTableXML := strings.ReplaceAll(v.Target, "..", "xl") pivotCacheRels := "xl/pivotTables/_rels/" + filepath.Base(v.Target) + ".rels" pivotTable, err := f.getPivotTable(sheet, pivotTableXML, pivotCacheRels) if err != nil { return pivotTables, err } pivotTables = append(pivotTables, pivotTable) } } return pivotTables, nil } // getPivotTableDataRange checking given if data range is a cell reference or // named reference (defined name or table name), and set pivot table data range. func (f *File) getPivotTableDataRange(opts *PivotTableOptions) error { if opts.DataRange == "" { return newPivotTableDataRangeError(ErrParameterRequired.Error()) } if opts.pivotDataRange != "" { return nil } if strings.Contains(opts.DataRange, "!") { opts.pivotDataRange = opts.DataRange return nil } tbls, err := f.getTables() if err != nil { return err } for sheetName, tables := range tbls { for _, table := range tables { if table.Name == opts.DataRange { opts.pivotDataRange, opts.namedDataRange = fmt.Sprintf("%s!%s", sheetName, table.Range), true return err } } } if !opts.namedDataRange { opts.pivotDataRange = f.getDefinedNameRefTo(opts.DataRange, opts.pivotSheetName) if opts.pivotDataRange != "" { opts.namedDataRange = true return nil } } return newPivotTableDataRangeError(ErrParameterInvalid.Error()) } // getPivotTable provides a function to get a pivot table definition by given // worksheet name, pivot table XML path and pivot cache relationship XML path. func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (PivotTableOptions, error) { var opts PivotTableOptions rels, err := f.relsReader(pivotCacheRels) if err != nil { return opts, err } var pivotCacheXML string for _, v := range rels.Relationships { if v.Type == SourceRelationshipPivotCache { pivotCacheXML = strings.ReplaceAll(v.Target, "..", "xl") break } } pc, err := f.pivotCacheReader(pivotCacheXML) if err != nil { return opts, err } pt, err := f.pivotTableReader(pivotTableXML) if err != nil { return opts, err } if pc.CacheSource.WorksheetSource == nil { return opts, newUnsupportedPivotCacheSourceType(pc.CacheSource.Type) } opts = PivotTableOptions{ pivotTableXML: pivotTableXML, pivotCacheXML: pivotCacheXML, pivotSheetName: sheet, DataRange: fmt.Sprintf("%s!%s", pc.CacheSource.WorksheetSource.Sheet, pc.CacheSource.WorksheetSource.Ref), PivotTableRange: fmt.Sprintf("%s!%s", sheet, pt.Location.Ref), Name: pt.Name, ClassicLayout: pt.GridDropZones, FieldPrintTitles: pt.FieldPrintTitles, ItemPrintTitles: pt.ItemPrintTitles, } if pc.CacheSource.WorksheetSource.Name != "" { opts.DataRange = pc.CacheSource.WorksheetSource.Name _ = f.getPivotTableDataRange(&opts) } setPtrFieldsVal([]string{ "RowGrandTotals", "ColGrandTotals", "ShowDrill", "UseAutoFormatting", "PageOverThenDown", "MergeItem", "CompactData", "ShowError", }, reflect.ValueOf(*pt), reflect.ValueOf(&opts).Elem()) if si := pt.PivotTableStyleInfo; si != nil { opts.ShowRowHeaders = si.ShowRowHeaders opts.ShowColHeaders = si.ShowColHeaders opts.ShowRowStripes = si.ShowRowStripes opts.ShowColStripes = si.ShowColStripes opts.ShowLastColumn = si.ShowLastColumn opts.PivotTableStyleName = si.Name } if err = f.getPivotTableDataRange(&opts); err != nil { return opts, err } f.extractPivotTableFields(pc.getPivotCacheFieldsName(), pt, &opts) return opts, err } // getPivotCacheFieldsName returns pivot table fields name list by order from // pivot cache fields. func (pc *xlsxPivotCacheDefinition) getPivotCacheFieldsName() []string { var order []string if pc.CacheFields != nil { for _, cf := range pc.CacheFields.CacheField { if cf != nil { order = append(order, cf.Name) } } } return order } // pivotTableReader provides a function to get the pointer to the structure // after deserialization of xl/pivotTables/pivotTable%d.xml. func (f *File) pivotTableReader(path string) (*xlsxPivotTableDefinition, error) { content, ok := f.Pkg.Load(path) pivotTable := &xlsxPivotTableDefinition{} if ok && content != nil { if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(pivotTable); err != nil && err != io.EOF { return nil, err } } return pivotTable, nil } // pivotCacheReader provides a function to get the pointer to the structure // after deserialization of xl/pivotCache/pivotCacheDefinition%d.xml. func (f *File) pivotCacheReader(path string) (*xlsxPivotCacheDefinition, error) { content, ok := f.Pkg.Load(path) pivotCache := &xlsxPivotCacheDefinition{} if ok && content != nil { if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(pivotCache); err != nil && err != io.EOF { return nil, err } } return pivotCache, nil } // extractPivotTableFields provides a function to extract all pivot table fields // settings by given pivot table fields. func (f *File) extractPivotTableFields(order []string, pt *xlsxPivotTableDefinition, opts *PivotTableOptions) { for fieldIdx, field := range pt.PivotFields.PivotField { if field.Axis == "axisRow" { opts.Rows = append(opts.Rows, extractPivotTableField(order[fieldIdx], field)) } if field.Axis == "axisCol" { opts.Columns = append(opts.Columns, extractPivotTableField(order[fieldIdx], field)) } if field.Axis == "axisPage" { opts.Filter = append(opts.Filter, extractPivotTableField(order[fieldIdx], field)) } } if pt.DataFields != nil { for _, field := range pt.DataFields.DataField { opts.Data = append(opts.Data, PivotTableField{ Data: order[field.Fld], Name: field.Name, Subtotal: cases.Title(language.English).String(field.Subtotal), NumFmt: field.NumFmtID, }) } } } // extractPivotTableField provides a function to extract pivot table field // settings by given pivot table fields. func extractPivotTableField(data string, fld *xlsxPivotField) PivotTableField { pivotTableField := PivotTableField{ Data: data, ShowAll: fld.ShowAll, InsertBlankRow: fld.InsertBlankRow, } setPtrFieldsVal([]string{"Compact", "Outline", "DefaultSubtotal"}, reflect.ValueOf(*fld), reflect.ValueOf(&pivotTableField).Elem()) return pivotTableField } // genPivotCacheDefinitionID generates a unique pivot table cache definition ID. func (f *File) genPivotCacheDefinitionID() int { var ( ID int decodeExtLst = new(decodeExtLst) decodeX14PivotCacheDefinition = new(decodeX14PivotCacheDefinition) ) f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") { pc, err := f.pivotCacheReader(k.(string)) if err != nil { return true } if pc.ExtLst != nil { _ = f.xmlNewDecoder(strings.NewReader("" + pc.ExtLst.Ext + "")).Decode(decodeExtLst) for _, ext := range decodeExtLst.Ext { if ext.URI == ExtURIPivotCacheDefinition { _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeX14PivotCacheDefinition) if ID < decodeX14PivotCacheDefinition.PivotCacheID { ID = decodeX14PivotCacheDefinition.PivotCacheID } } } } } return true }) return ID + 1 } // deleteWorkbookPivotCache remove workbook pivot cache and pivot cache // relationships. func (f *File) deleteWorkbookPivotCache(opt PivotTableOptions) error { rID, err := f.deleteWorkbookRels(SourceRelationshipPivotCache, strings.TrimPrefix(strings.TrimPrefix(opt.pivotCacheXML, "/"), "xl/")) if err != nil { return err } wb, err := f.workbookReader() if err != nil { return err } if wb.PivotCaches != nil { for i, pivotCache := range wb.PivotCaches.PivotCache { if pivotCache.RID == rID { wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache[:i], wb.PivotCaches.PivotCache[i+1:]...) } } if len(wb.PivotCaches.PivotCache) == 0 { wb.PivotCaches = nil } } return err } // DeletePivotTable delete a pivot table by giving the worksheet name and pivot // table name. Note that this function does not clean cell values in the pivot // table range. func (f *File) DeletePivotTable(sheet, name string) error { sheetXML, ok := f.getSheetXMLPath(sheet) if !ok { return ErrSheetNotExist{sheet} } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXML, "xl/worksheets/") + ".rels" sheetRels, err := f.relsReader(rels) if err != nil { return err } if sheetRels == nil { sheetRels = &xlsxRelationships{} } opts, err := f.GetPivotTables(sheet) if err != nil { return err } f.clearCalcCache() pivotTableCaches := map[string]int{} pivotTables, _ := f.getPivotTables() for _, sheetPivotTables := range pivotTables { for _, sheetPivotTable := range sheetPivotTables { pivotTableCaches[sheetPivotTable.pivotCacheXML]++ } } for _, v := range sheetRels.Relationships { for _, opt := range opts { if v.Type == SourceRelationshipPivotTable { pivotTableXML := strings.ReplaceAll(v.Target, "..", "xl") if opt.Name == name && opt.pivotTableXML == pivotTableXML { if pivotTableCaches[opt.pivotCacheXML] == 1 { err = f.deleteWorkbookPivotCache(opt) } f.deleteSheetRelationships(sheet, v.ID) return err } } } } return newNoExistTableError(name) } // getPivotTables provides a function to get all pivot tables in a workbook. func (f *File) getPivotTables() (map[string][]PivotTableOptions, error) { pivotTables := map[string][]PivotTableOptions{} for _, sheetName := range f.GetSheetList() { pts, err := f.GetPivotTables(sheetName) e := ErrSheetNotExist{sheetName} if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() { return pivotTables, err } pivotTables[sheetName] = append(pivotTables[sheetName], pts...) } return pivotTables, nil } ================================================ FILE: pivotTable_test.go ================================================ package excelize import ( "fmt" "math/rand" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestPivotTable(t *testing.T) { f := NewFile() // Create some data in a sheet month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} year := []int{2017, 2018, 2019} types := []string{"Meat", "Dairy", "Beverages", "Produce"} region := []string{"East", "West", "North", "South"} assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})) for row := 2; row < 32; row++ { assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])) } expected := &PivotTableOptions{ pivotTableXML: "xl/pivotTables/pivotTable1.xml", pivotCacheXML: "xl/pivotCache/pivotCacheDefinition1.xml", DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Name: "PivotTable1", Rows: []PivotTableField{{Data: "Month", ShowAll: true, DefaultSubtotal: true}, {Data: "Year"}}, Filter: []PivotTableField{{Data: "Region"}}, Columns: []PivotTableField{{Data: "Type", ShowAll: true, InsertBlankRow: true, DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum", NumFmt: 38}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ClassicLayout: true, ShowError: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, FieldPrintTitles: true, ItemPrintTitles: true, PivotTableStyleName: "PivotStyleLight16", } assert.NoError(t, f.AddPivotTable(expected)) // Test get pivot table pivotTables, err := f.GetPivotTables("Sheet1") assert.NoError(t, err) assert.Len(t, pivotTables, 1) assert.Equal(t, *expected, pivotTables[0]) // Use different order of coordinate tests assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Test get pivot table with default style name pivotTables, err = f.GetPivotTables("Sheet1") assert.NoError(t, err) assert.Len(t, pivotTables, 2) assert.Equal(t, "PivotStyleLight16", pivotTables[1].PivotTableStyleName) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!W2:AC34", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Region"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Count", Name: "Summarize by Count"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G42:W55", Rows: []PivotTableField{{Data: "Month"}}, Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "CountNums", Name: "Summarize by CountNums"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!AE2:AG33", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Create pivot table with empty subtotal field name and specified style assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!AJ2:AP135", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Filter: []PivotTableField{{Data: "Region"}}, Columns: []PivotTableField{}, Data: []PivotTableField{{Subtotal: "Sum", Name: "Summarize by Sum"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, PivotTableStyleName: "PivotStyleLight19", })) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet2!A1:AN17", Rows: []PivotTableField{{Data: "Month"}}, Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type", DefaultSubtotal: true}, {Data: "Year"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min", NumFmt: 32}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Test get pivot table with across worksheet data range pivotTables, err = f.GetPivotTables("Sheet2") assert.NoError(t, err) assert.Len(t, pivotTables, 1) assert.Equal(t, "Sheet1!A1:E31", pivotTables[0].DataRange) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet2!A20:AR60", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Type"}}, Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product", NumFmt: 32}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Create pivot table with many data, many rows, many cols and defined name assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "dataRange", RefersTo: "Sheet1!A1:E31", Comment: "Pivot Table Data Range", Scope: "Sheet2", })) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "dataRange", PivotTableRange: "Sheet2!A65:AJ100", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type"}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Sum of Sales", NumFmt: -1}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales", NumFmt: 38}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Test empty pivot table options assert.Equal(t, ErrParameterRequired, f.AddPivotTable(nil)) // Test add pivot table with custom name which exceeds the max characters limit assert.Equal(t, ErrNameLength, f.AddPivotTable(&PivotTableOptions{ DataRange: "dataRange", PivotTableRange: "Sheet2!A65:AJ100", Name: strings.Repeat("c", MaxFieldLength+1), })) // Test invalid data range assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:A1", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test the data range of the worksheet that is not declared assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{ DataRange: "A1:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test the worksheet declared in the data range does not exist assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{ DataRange: "SheetN!A1:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test the pivot table range of the worksheet that is not declared assert.Equal(t, newPivotTableRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test the worksheet declared in the pivot table range does not exist assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "SheetN!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test not exists worksheet in data range assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{ DataRange: "SheetN!A1:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) // Test invalid row number in data range assert.Equal(t, newPivotTableDataRangeError(newCellNameToCoordinatesError("A0", newInvalidCellNameError("A0")).Error()), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A0:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable1.xlsx"))) // Test with field names that exceed the length limit and invalid subtotal assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", MaxFieldLength+1)}}, })) // Test with same data field appears both in the pivot table column fields and filter fields assert.Equal(t, newPivotTableColFieldsError([]string{"Type"}), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, Filter: []PivotTableField{{Data: "Type"}}, })) // Test with same data field appears both in the pivot table row fields and filter fields assert.Equal(t, newPivotTableRowFieldsError([]string{"Month"}), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, Filter: []PivotTableField{{Data: "Month"}}, })) // Test delete pivot table pivotTables, err = f.GetPivotTables("Sheet1") assert.Len(t, pivotTables, 7) assert.NoError(t, err) assert.NoError(t, f.DeletePivotTable("Sheet1", "PivotTable1")) pivotTables, err = f.GetPivotTables("Sheet1") assert.Len(t, pivotTables, 6) assert.NoError(t, err) // Test add pivot table with invalid sheet name assert.Error(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet:1!A1:E31", PivotTableRange: "Sheet:1!G2:M34", Rows: []PivotTableField{{Data: "Year"}}, }), ErrSheetNameInvalid) // Test add pivot table with enable ClassicLayout and CompactData in the same time assert.Error(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", CompactData: true, ClassicLayout: true, }), ErrPivotTableClassicLayout) // Test delete pivot table with not exists worksheet assert.EqualError(t, f.DeletePivotTable("SheetN", "PivotTable1"), "sheet SheetN does not exist") // Test delete pivot table with not exists pivot table name assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTableN"), "table PivotTableN does not exist") // Test adjust range with invalid range _, _, err = f.adjustRange("") assert.Error(t, err, ErrParameterRequired) // Test adjust range with incorrect range _, _, err = f.adjustRange("sheet1!") assert.EqualError(t, err, "parameter is invalid") // Test get table fields order with empty data range _, err = f.getTableFieldsOrder(&PivotTableOptions{}) assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) // Test add pivot cache with empty data range assert.EqualError(t, f.addPivotCache(&PivotTableOptions{}), "parameter 'DataRange' parsing error: parameter is required") // Test add pivot table with empty options assert.EqualError(t, f.addPivotTable(0, 0, &PivotTableOptions{}), "parameter 'PivotTableRange' parsing error: parameter is required") // Test add pivot table with invalid data range assert.EqualError(t, f.addPivotTable(0, 0, &PivotTableOptions{}), "parameter 'PivotTableRange' parsing error: parameter is required") // Test add pivot fields with empty data range assert.EqualError(t, f.addPivotFields(nil, &PivotTableOptions{ DataRange: "A1:E31", PivotTableRange: "Sheet1!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales"}}, }), `parameter 'DataRange' parsing error: parameter is invalid`) // Test get pivot fields index with empty data range _, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOptions{}) assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) // Test add pivot table with unsupported charset content types. f = NewFile() assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})) f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Rows: []PivotTableField{{Data: "Year"}}, }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get pivot table without pivot table f = NewFile() pivotTables, err = f.GetPivotTables("Sheet1") assert.NoError(t, err) assert.Len(t, pivotTables, 0) // Test get pivot table with not exists worksheet _, err = f.GetPivotTables("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get pivot table with unsupported charset worksheet relationships f.Pkg.Store("xl/worksheets/_rels/sheet1.xml.rels", MacintoshCyrillicCharset) _, err = f.GetPivotTables("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get pivot table with unsupported charset pivot cache definition f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx")) assert.NoError(t, err) f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", MacintoshCyrillicCharset) _, err = f.GetPivotTables("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get pivot table with unsupported charset pivot table relationships f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx")) assert.NoError(t, err) f.Pkg.Store("xl/pivotTables/_rels/pivotTable1.xml.rels", MacintoshCyrillicCharset) _, err = f.GetPivotTables("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get pivot table with unsupported charset pivot table f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx")) assert.NoError(t, err) f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset) _, err = f.GetPivotTables("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") _, err = f.getPivotTables() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test get pivot table with unsupported pivot table cache source type f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx")) assert.NoError(t, err) f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value)) _, err = f.GetPivotTables("Sheet1") assert.Equal(t, err, newUnsupportedPivotCacheSourceType("external")) assert.NoError(t, f.Close()) } func TestPivotTableDataRange(t *testing.T) { f := NewFile() // Create table in a worksheet assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) for row := 2; row < 6; row++ { assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), rand.Intn(10))) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), rand.Intn(10))) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), rand.Intn(10))) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(10))) } // Test add pivot table with table data range opts := PivotTableOptions{ DataRange: "Table1", PivotTableRange: "Sheet1!G2:K7", Rows: []PivotTableField{{Data: "Column1"}}, Columns: []PivotTableField{{Data: "Column2"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, ShowError: true, PivotTableStyleName: "PivotStyleLight16", } assert.NoError(t, f.AddPivotTable(&opts)) assert.NoError(t, f.DeletePivotTable("Sheet1", "PivotTable1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable2.xlsx"))) assert.NoError(t, f.Close()) assert.NoError(t, f.AddPivotTable(&opts)) // Test delete pivot table with unsupported table relationships charset f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "XML syntax error on line 1: invalid UTF-8") // Test delete pivot table with unsupported worksheet relationships charset f.Relationships.Delete("xl/worksheets/_rels/sheet1.xml.rels") f.Pkg.Store("xl/worksheets/_rels/sheet1.xml.rels", MacintoshCyrillicCharset) assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "XML syntax error on line 1: invalid UTF-8") // Test delete pivot table without worksheet relationships f.Relationships.Delete("xl/worksheets/_rels/sheet1.xml.rels") f.Pkg.Delete("xl/worksheets/_rels/sheet1.xml.rels") assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "table PivotTable1 does not exist") t.Run("data_range_with_empty_column", func(t *testing.T) { // Test add pivot table with data range doesn't organized as a list with labeled columns f := NewFile() // Create some data in a sheet month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} types := []string{"Meat", "Dairy", "Beverages", "Produce"} assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "", "Type"})) for row := 2; row < 32; row++ { assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])) } assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!A1:E31", PivotTableRange: "Sheet1!G2:M34", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Type"}}, })) }) } func TestParseFormatPivotTableSet(t *testing.T) { f := NewFile() // Create table in a worksheet assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) // Test parse format pivot table options with unsupported table relationships charset f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) _, _, err := f.parseFormatPivotTableSet(&PivotTableOptions{ DataRange: "Table1", PivotTableRange: "Sheet1!G2:K7", Rows: []PivotTableField{{Data: "Column1"}}, }) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestAddPivotRowFields(t *testing.T) { f := NewFile() // Test invalid data range assert.EqualError(t, f.addPivotRowFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{ DataRange: "Sheet1!A1:A1", }), `parameter 'DataRange' parsing error: parameter is invalid`) } func TestAddPivotPageFields(t *testing.T) { f := NewFile() // Test invalid data range assert.EqualError(t, f.addPivotPageFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{ DataRange: "Sheet1!A1:A1", }), `parameter 'DataRange' parsing error: parameter is invalid`) } func TestAddPivotDataFields(t *testing.T) { f := NewFile() // Test invalid data range assert.EqualError(t, f.addPivotDataFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{ DataRange: "Sheet1!A1:A1", }), `parameter 'DataRange' parsing error: parameter is invalid`) } func TestAddPivotColFields(t *testing.T) { f := NewFile() // Test invalid data range assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{ DataRange: "Sheet1!A1:A1", Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, }), `parameter 'DataRange' parsing error: parameter is invalid`) } func TestGetPivotFieldsOrder(t *testing.T) { f := NewFile() // Test get table fields order with not exist worksheet _, err := f.getTableFieldsOrder(&PivotTableOptions{DataRange: "SheetN!A1:E31"}) assert.EqualError(t, err, "sheet SheetN does not exist") // Create table in a worksheet assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) // Test get table fields order with unsupported table relationships charset f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) _, err = f.getTableFieldsOrder(&PivotTableOptions{DataRange: "Table"}) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestGetPivotTableFieldName(t *testing.T) { f := NewFile() assert.Empty(t, f.getPivotTableFieldName("-", []PivotTableField{})) } func TestGetPivotTableFieldOptions(t *testing.T) { f := NewFile() _, ok := f.getPivotTableFieldOptions("-", []PivotTableField{}) assert.False(t, ok) } func TestGenPivotCacheDefinitionID(t *testing.T) { f := NewFile() // Test generate pivot table cache definition ID with unsupported charset f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", MacintoshCyrillicCharset) assert.Equal(t, 1, f.genPivotCacheDefinitionID()) assert.NoError(t, f.Close()) } func TestDeleteWorkbookPivotCache(t *testing.T) { f := NewFile() // Test delete workbook pivot table cache with unsupported workbook charset f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteWorkbookPivotCache(PivotTableOptions{pivotCacheXML: "pivotCache/pivotCacheDefinition1.xml"}), "XML syntax error on line 1: invalid UTF-8") // Test delete workbook pivot table cache with unsupported workbook relationships charset f.Relationships.Delete("xl/_rels/workbook.xml.rels") f.Pkg.Store("xl/_rels/workbook.xml.rels", MacintoshCyrillicCharset) assert.EqualError(t, f.deleteWorkbookPivotCache(PivotTableOptions{pivotCacheXML: "pivotCache/pivotCacheDefinition1.xml"}), "XML syntax error on line 1: invalid UTF-8") } ================================================ FILE: rows.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" "math" "os" "strconv" "strings" "github.com/tiendc/go-deepcopy" ) // duplicateHelperFunc defines functions to duplicate helper. var duplicateHelperFunc = [3]func(*File, *xlsxWorksheet, string, int, int) error{ func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { return f.duplicateConditionalFormat(ws, sheet, row, row2) }, func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { return f.duplicateDataValidations(ws, sheet, row, row2) }, func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { return f.duplicateMergeCells(ws, sheet, row, row2) }, } // GetRows return all the rows in a sheet by given worksheet name, returned as // a two-dimensional array, where the value of the cell is converted to the // string type. If the cell format can be applied to the value of the cell, // the applied value will be used, otherwise the original value will be used. // GetRows fetched the rows with value or formula cells, the continually blank // cells in the tail of each row will be skipped, so the length of each row // may be inconsistent. // // For example, get and traverse the value of all cells by rows on a worksheet // named 'Sheet1': // // rows, err := f.GetRows("Sheet1") // if err != nil { // fmt.Println(err) // return // } // for _, row := range rows { // for _, colCell := range row { // fmt.Print(colCell, "\t") // } // fmt.Println() // } func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) { rows, err := f.Rows(sheet) if err != nil { return nil, err } results, cur, maxVal := make([][]string, 0, 64), 0, 0 for rows.Next() { cur++ row, err := rows.Columns(opts...) if err != nil { break } if len(row) > 0 { if emptyRows := cur - maxVal - 1; emptyRows > 0 { results = append(results, make([][]string, emptyRows)...) } results = append(results, row) maxVal = cur } } return results[:maxVal], rows.Close() } // Rows defines an iterator to a sheet. type Rows struct { err error curRow, seekRow int needClose, rawCellValue bool sheet string f *File tempFile *os.File sst *xlsxSST decoder *xml.Decoder token xml.Token curRowOpts, seekRowOpts RowOpts } // Next will return true if it finds the next row element. func (rows *Rows) Next() bool { rows.seekRow++ if rows.curRow >= rows.seekRow { rows.curRowOpts = rows.seekRowOpts return true } for { token, _ := rows.decoder.Token() if token == nil { return false } switch xmlElement := token.(type) { case xml.StartElement: if xmlElement.Name.Local == "row" { rows.curRow++ if rowNum, _ := attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } rows.token = token rows.curRowOpts = extractRowOpts(xmlElement.Attr) return true } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return false } } } } // GetRowOpts will return the RowOpts of the current row. func (rows *Rows) GetRowOpts() RowOpts { return rows.curRowOpts } // Error will return the error when the error occurs. func (rows *Rows) Error() error { return rows.err } // Close closes the open worksheet XML file in the system temporary // directory. func (rows *Rows) Close() error { tempFile := rows.tempFile rows.tempFile = nil if tempFile != nil { return tempFile.Close() } return nil } // Columns return the current row's column values. This fetches the worksheet // data as a stream, returns each cell in a row as is, and will not skip empty // rows in the tail of the worksheet. func (rows *Rows) Columns(opts ...Options) ([]string, error) { if rows.curRow > rows.seekRow { return nil, nil } var rowIterator rowXMLIterator var token xml.Token rows.rawCellValue = rows.f.getOptions(opts...).RawCellValue if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } for { if rows.token != nil { token = rows.token } else if token, _ = rows.decoder.Token(); token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowNum := 0 if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } else if rows.token == nil { rows.curRow++ } rows.token = token rows.seekRowOpts = extractRowOpts(xmlElement.Attr) if rows.curRow > rows.seekRow { rows.token = nil return rowIterator.cells, rowIterator.err } } if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil { rows.token = nil return rowIterator.cells, rowIterator.err } rows.token = nil case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return rowIterator.cells, rowIterator.err } } } return rowIterator.cells, rowIterator.err } // extractRowOpts extract row element attributes. func extractRowOpts(attrs []xml.Attr) RowOpts { rowOpts := RowOpts{Height: defaultRowHeight} if styleID, err := attrValToInt("s", attrs); err == nil && styleID > 0 && styleID < MaxCellStyles { rowOpts.StyleID = styleID } if hidden, err := attrValToBool("hidden", attrs); err == nil { rowOpts.Hidden = hidden } if height, err := attrValToFloat("ht", attrs); err == nil { rowOpts.Height = height } return rowOpts } // appendSpace append blank characters to slice by given length and source slice. func appendSpace(l int, s []string) []string { for i := 1; i < l; i++ { s = append(s, "") } return s } // rowXMLIterator defined runtime use field for the worksheet row SAX parser. type rowXMLIterator struct { err error inElement string cellCol, cellRow int cells []string } // rowXMLHandler parse the row XML element of the worksheet. func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, raw bool) { if rowIterator.inElement == "c" { rowIterator.cellCol++ colCell := xlsxC{} _ = colCell.cellXMLHandler(rows.decoder, xmlElement) if colCell.R != "" { if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil { return } } blank := rowIterator.cellCol - len(rowIterator.cells) if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil { rowIterator.cells = append(appendSpace(blank, rowIterator.cells), val) } } } // cellXMLAttrHandler parse the cell XML element attributes of the worksheet. func (cell *xlsxC) cellXMLAttrHandler(start *xml.StartElement) error { for _, attr := range start.Attr { switch attr.Name.Local { case "r": cell.R = attr.Value case "s": val, err := strconv.ParseInt(attr.Value, 10, 64) if err != nil { return err } if math.MinInt <= val && val <= math.MaxInt { cell.S = int(val) } case "t": cell.T = attr.Value default: } } return nil } // cellXMLHandler parse the cell XML element of the worksheet. func (cell *xlsxC) cellXMLHandler(decoder *xml.Decoder, start *xml.StartElement) error { cell.XMLName = start.Name err := cell.cellXMLAttrHandler(start) if err != nil { return err } for { tok, err := decoder.Token() if err != nil { return err } var se xml.StartElement switch el := tok.(type) { case xml.StartElement: se = el switch se.Name.Local { case "v": err = decoder.DecodeElement(&cell.V, &se) case "f": err = decoder.DecodeElement(&cell.F, &se) case "is": err = decoder.DecodeElement(&cell.IS, &se) } if err != nil { return err } case xml.EndElement: if el == start.End() { return nil } } } } // Rows returns a rows iterator, used for streaming reading data for a // worksheet with a large data. This function is concurrency safe. For // example: // // rows, err := f.Rows("Sheet1") // if err != nil { // fmt.Println(err) // return // } // for rows.Next() { // row, err := rows.Columns() // if err != nil { // fmt.Println(err) // } // for _, colCell := range row { // fmt.Print(colCell, "\t") // } // fmt.Println() // } // if err = rows.Close(); err != nil { // fmt.Println(err) // } func (f *File) Rows(sheet string) (*Rows, error) { if err := checkSheetName(sheet); err != nil { return nil, err } name, ok := f.getSheetXMLPath(sheet) if !ok { return nil, ErrSheetNotExist{sheet} } if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { ws := worksheet.(*xlsxWorksheet) ws.mu.Lock() defer ws.mu.Unlock() // Flush data output, _ := xml.Marshal(ws) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var err error rows := Rows{f: f, sheet: name} rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name) return &rows, err } // getFromStringItem build shared string item offset list from system temporary // file at one time, and return value by given to string index. func (f *File) getFromStringItem(index int) string { if f.sharedStringTemp != nil { if len(f.sharedStringItem) <= index { return strconv.Itoa(index) } offsetRange := f.sharedStringItem[index] if len(offsetRange) != 2 || offsetRange[0] > offsetRange[1] { return strconv.Itoa(index) } buf := make([]byte, offsetRange[1]-offsetRange[0]) if _, err := f.sharedStringTemp.ReadAt(buf, int64(offsetRange[0])); err != nil { return strconv.Itoa(index) } return string(buf) } needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings) if needClose && err == nil { defer func() { err = tempFile.Close() }() } f.sharedStringItem = [][]uint{} f.sharedStringTemp, _ = os.CreateTemp(f.options.TmpDir, "excelize-") f.tempFiles.Store(defaultTempFileSST, f.sharedStringTemp.Name()) var ( inElement string i, offset uint ) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: inElement = xmlElement.Name.Local if inElement == "si" { si := xlsxSI{} _ = decoder.DecodeElement(&si, &xmlElement) startIdx := offset n, _ := f.sharedStringTemp.WriteString(si.String()) offset += uint(n) f.sharedStringItem = append(f.sharedStringItem, []uint{startIdx, offset}) i++ } } } return f.getFromStringItem(index) } // xmlDecoder creates XML decoder by given path in the zip from memory data // or system temporary file. func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) { var ( content []byte err error tempFile *os.File ) if content = f.readXML(name); len(content) > 0 { return false, f.xmlNewDecoder(bytes.NewReader(content)), tempFile, err } tempFile, err = f.readTemp(name) return true, f.xmlNewDecoder(tempFile), tempFile, err } // SetRowHeight provides a function to set the height of a single row. If the // value of height is 0, will hide the specified row, if the value of height is // -1, will unset the custom row height. For example, set the height of the // first row in Sheet1: // // err := f.SetRowHeight("Sheet1", 1, 50) func (f *File) SetRowHeight(sheet string, row int, height float64) error { if row < 1 { return newInvalidRowNumberError(row) } if height > MaxRowHeight { return ErrMaxRowHeight } if height < -1 { return ErrParameterInvalid } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.prepareSheetXML(0, row) rowIdx := row - 1 if height == -1 { ws.SheetData.Row[rowIdx].Ht = nil ws.SheetData.Row[rowIdx].CustomHeight = false return err } ws.SheetData.Row[rowIdx].Ht = float64Ptr(height) ws.SheetData.Row[rowIdx].CustomHeight = true return err } // getRowHeight provides a function to get row height in pixels by given sheet // name and row number. func (f *File) getRowHeight(sheet string, row int) int { ws, _ := f.workSheetReader(sheet) ws.mu.Lock() defer ws.mu.Unlock() height := -1.0 for i := range ws.SheetData.Row { v := &ws.SheetData.Row[i] if v.R == row && v.Ht != nil { height = *v.Ht break } } if height != -1.0 { return int(convertRowHeightToPixels(height)) } if ws.SheetFormatPr != nil && ws.SheetFormatPr.DefaultRowHeight > 0 { return int(convertRowHeightToPixels(ws.SheetFormatPr.DefaultRowHeight)) } // Optimization for when the row heights haven't changed. return int(defaultRowHeightPixels) } // GetRowHeight provides a function to get row height by given worksheet name // and row number. For example, get the height of the first row in Sheet1: // // height, err := f.GetRowHeight("Sheet1", 1) func (f *File) GetRowHeight(sheet string, row int) (float64, error) { if row < 1 { return defaultRowHeight, newInvalidRowNumberError(row) } ht := defaultRowHeight ws, err := f.workSheetReader(sheet) if err != nil { return ht, err } if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight { ht = ws.SheetFormatPr.DefaultRowHeight } if row > len(ws.SheetData.Row) { return ht, nil // it will be better to use 0, but we take care with BC } for _, v := range ws.SheetData.Row { if v.R == row && v.Ht != nil { return *v.Ht, nil } } // Optimization for when the row heights haven't changed. return ht, nil } // sharedStringsReader provides a function to get the pointer to the structure // after deserialization of xl/sharedStrings.xml. func (f *File) sharedStringsReader() (*xlsxSST, error) { var err error f.mu.Lock() defer f.mu.Unlock() relPath := f.getWorkbookRelsPath() if f.SharedStrings == nil { var sharedStrings xlsxSST ss := f.readXML(defaultXMLPathSharedStrings) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))). Decode(&sharedStrings); err != nil && err != io.EOF { return f.SharedStrings, err } if sharedStrings.Count == 0 { sharedStrings.Count = len(sharedStrings.SI) } if sharedStrings.UniqueCount == 0 { sharedStrings.UniqueCount = sharedStrings.Count } f.SharedStrings = &sharedStrings for i := range sharedStrings.SI { if sharedStrings.SI[i].T != nil { f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i } } if err = f.addContentTypePart(0, "sharedStrings"); err != nil { return f.SharedStrings, err } rels, err := f.relsReader(relPath) if err != nil { return f.SharedStrings, err } for _, rel := range rels.Relationships { if rel.Target == "/xl/sharedStrings.xml" { return f.SharedStrings, nil } } // Update workbook.xml.rels f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "") } return f.SharedStrings, nil } // SetRowVisible provides a function to set visible of a single row by given // worksheet name and Excel row number. For example, hide row 2 in Sheet1: // // err := f.SetRowVisible("Sheet1", 2, false) func (f *File) SetRowVisible(sheet string, row int, visible bool) error { if row < 1 { return newInvalidRowNumberError(row) } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.prepareSheetXML(0, row) ws.SheetData.Row[row-1].Hidden = !visible return nil } // GetRowVisible provides a function to get visible of a single row by given // worksheet name and Excel row number. For example, get visible state of row // 2 in Sheet1: // // visible, err := f.GetRowVisible("Sheet1", 2) func (f *File) GetRowVisible(sheet string, row int) (bool, error) { if row < 1 { return false, newInvalidRowNumberError(row) } ws, err := f.workSheetReader(sheet) if err != nil { return false, err } if row > len(ws.SheetData.Row) { return false, nil } return !ws.SheetData.Row[row-1].Hidden, nil } // SetRowOutlineLevel provides a function to set outline level number of a // single row by given worksheet name and row number. The range of 'level' // parameter value from 1 to 7. For example, outline row 2 in Sheet1 to level 1: // // err := f.SetRowOutlineLevel("Sheet1", 2, 1) func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error { if row < 1 { return newInvalidRowNumberError(row) } if level > 7 || level < 1 { return ErrOutlineLevel } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.prepareSheetXML(0, row) ws.SheetData.Row[row-1].OutlineLevel = level return nil } // GetRowOutlineLevel provides a function to get outline level number of a // single row by given worksheet name and Excel row number. For example, get // outline number of row 2 in Sheet1: // // level, err := f.GetRowOutlineLevel("Sheet1", 2) func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) { if row < 1 { return 0, newInvalidRowNumberError(row) } ws, err := f.workSheetReader(sheet) if err != nil { return 0, err } if row > len(ws.SheetData.Row) { return 0, nil } return ws.SheetData.Row[row-1].OutlineLevel, nil } // RemoveRow provides a function to remove single row by given worksheet name // and Excel row number. For example, remove row 3 in Sheet1: // // err := f.RemoveRow("Sheet1", 3) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) RemoveRow(sheet string, row int) error { if row < 1 { return newInvalidRowNumberError(row) } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.formulaSI.Clear() if row > len(ws.SheetData.Row) { return f.adjustHelper(sheet, rows, row, -1) } for rowIdx := range ws.SheetData.Row { v := &ws.SheetData.Row[rowIdx] if v.R == row { for _, c := range v.C { if err := f.removeFormula(&c, ws, sheet); err != nil { return err } } } } keep := 0 for rowIdx := range ws.SheetData.Row { v := &ws.SheetData.Row[rowIdx] if v.R != row { ws.SheetData.Row[keep] = *v keep++ } } ws.SheetData.Row = ws.SheetData.Row[:keep] return f.adjustHelper(sheet, rows, row, -1) } // InsertRows provides a function to insert new rows after the given Excel row // number starting from 1 and number of rows. For example, create two rows // before row 3 in Sheet1: // // err := f.InsertRows("Sheet1", 3, 2) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) InsertRows(sheet string, row, n int) error { if row < 1 { return newInvalidRowNumberError(row) } if row >= TotalRows || n >= TotalRows { return ErrMaxRows } if n < 1 { return ErrParameterInvalid } return f.adjustHelper(sheet, rows, row, n) } // DuplicateRow inserts a copy of specified row (by its Excel row number) below // // err := f.DuplicateRow("Sheet1", 2) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) DuplicateRow(sheet string, row int) error { return f.DuplicateRowTo(sheet, row, row+1) } // DuplicateRowTo inserts a copy of specified row by it Excel number // to specified row position moving down exists rows after target position // // err := f.DuplicateRowTo("Sheet1", 2, 7) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { if row < 1 { return newInvalidRowNumberError(row) } ws, err := f.workSheetReader(sheet) if err != nil { return err } if row2 < 1 || row == row2 { return err } var ok bool var rowCopy xlsxRow for i, r := range ws.SheetData.Row { if r.R == row { err = deepcopy.Copy(&rowCopy, ws.SheetData.Row[i]) ok = true break } } if err := f.adjustHelper(sheet, rows, row2, 1); err != nil { return err } if !ok { return err } idx2 := -1 for i, r := range ws.SheetData.Row { if r.R == row2 { idx2 = i break } } rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) rowCopy.adjustSingleRowDimensions(row2 - row) _ = f.adjustSingleRowFormulas(sheet, sheet, &rowCopy, row, row2-row, true) if idx2 != -1 { ws.SheetData.Row[idx2] = rowCopy } else { ws.SheetData.Row = append(ws.SheetData.Row, rowCopy) } for _, fn := range duplicateHelperFunc { if err := fn(f, ws, sheet, row, row2); err != nil { return err } } return err } // duplicateSQRefHelper provides a function to adjust conditional formatting and // data validations cell reference when duplicate rows. func duplicateSQRefHelper(row, row2 int, ref string) (string, error) { if !strings.Contains(ref, ":") { ref += ":" + ref } abs := strings.Contains(ref, "$") coordinates, err := rangeRefToCoordinates(ref) if err != nil { return "", err } x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if y1 == y2 && y1 == row { if ref, err = coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil { return "", err } return ref, err } return "", err } // duplicateConditionalFormat create conditional formatting for the destination // row if there are conditional formats in the copied row. func (f *File) duplicateConditionalFormat(ws *xlsxWorksheet, sheet string, row, row2 int) error { var cfs []*xlsxConditionalFormatting for _, cf := range ws.ConditionalFormatting { if cf != nil { var SQRef []string for _, ref := range strings.Split(cf.SQRef, " ") { coordinates, err := duplicateSQRefHelper(row, row2, ref) if err != nil { return err } if coordinates != "" { SQRef = append(SQRef, coordinates) } } if len(SQRef) > 0 { var cfCopy xlsxConditionalFormatting _ = deepcopy.Copy(&cfCopy, *cf) cfCopy.SQRef = strings.Join(SQRef, " ") cfs = append(cfs, &cfCopy) } } } ws.ConditionalFormatting = append(ws.ConditionalFormatting, cfs...) return nil } // duplicateDataValidations create data validations for the destination row if // there are data validation rules in the copied row. func (f *File) duplicateDataValidations(ws *xlsxWorksheet, sheet string, row, row2 int) error { if ws.DataValidations == nil { return nil } var dvs []*xlsxDataValidation for _, dv := range ws.DataValidations.DataValidation { if dv != nil { var SQRef []string for _, ref := range strings.Split(dv.Sqref, " ") { coordinates, err := duplicateSQRefHelper(row, row2, ref) if err != nil { return err } if coordinates != "" { SQRef = append(SQRef, coordinates) } } if len(SQRef) > 0 { var dvCopy xlsxDataValidation _ = deepcopy.Copy(&dvCopy, *dv) dvCopy.Sqref = strings.Join(SQRef, " ") dvs = append(dvs, &dvCopy) } } } ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dvs...) return nil } // duplicateMergeCells merge cells in the destination row if there are single // row merged cells in the copied row. func (f *File) duplicateMergeCells(ws *xlsxWorksheet, sheet string, row, row2 int) error { if ws.MergeCells == nil { return nil } if row > row2 { row++ } for _, rng := range ws.MergeCells.Cells { coordinates, err := rangeRefToCoordinates(rng.Ref) if err != nil { return err } if coordinates[1] < row2 && row2 < coordinates[3] { return nil } } for i := 0; i < len(ws.MergeCells.Cells); i++ { mergedCells := ws.MergeCells.Cells[i] coordinates, _ := rangeRefToCoordinates(mergedCells.Ref) x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] if y1 == y2 && y1 == row { from, _ := CoordinatesToCellName(x1, row2) to, _ := CoordinatesToCellName(x2, row2) if err := f.MergeCell(sheet, from, to); err != nil { return err } } } return nil } // checkRow provides a function to check and fill each column element for all // rows and make that is continuous in a worksheet of XML. For example: // // // // // // // // // in this case, we should to change it to // // // // // // // // // // // // Notice: this method could be very slow for large spreadsheets (more than // 3000 rows one sheet). func (ws *xlsxWorksheet) checkRow() error { for rowIdx := range ws.SheetData.Row { rowData := &ws.SheetData.Row[rowIdx] colCount := len(rowData.C) if colCount == 0 { continue } // check and fill the cell without r attribute in a row element rCount := 0 for idx, cell := range rowData.C { rCount++ if cell.R != "" { lastR, _, err := CellNameToCoordinates(cell.R) if err != nil { return err } if lastR > rCount { rCount = lastR } continue } rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1) } lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R) if err != nil { return err } if colCount < lastCol { sourceList := rowData.C targetList := make([]xlsxC, 0, lastCol) rowData.C = ws.SheetData.Row[rowIdx].C[:0] for colIdx := 0; colIdx < lastCol; colIdx++ { cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1) if err != nil { return err } targetList = append(targetList, xlsxC{R: cellName}) } rowData.C = targetList for colIdx := range sourceList { colData := &sourceList[colIdx] colNum, _, err := CellNameToCoordinates(colData.R) if err != nil { return err } ws.SheetData.Row[rowIdx].C[colNum-1] = *colData } } } return nil } // hasAttr determine if row non-default attributes. func (r *xlsxRow) hasAttr() bool { return r.Spans != "" || r.S != 0 || r.CustomFormat || r.Ht != nil || r.Hidden || r.CustomHeight || r.OutlineLevel != 0 || r.Collapsed || r.ThickTop || r.ThickBot || r.Ph } // SetRowStyle provides a function to set the style of rows by given worksheet // name, row range, and style ID. Note that this will overwrite the existing // styles for the rows, it won't append or merge style with existing styles. // // For example set style of row 1 on Sheet1: // // err := f.SetRowStyle("Sheet1", 1, 1, styleID) // // Set style of rows 1 to 10 on Sheet1: // // err := f.SetRowStyle("Sheet1", 1, 10, styleID) func (f *File) SetRowStyle(sheet string, start, end, styleID int) error { if end < start { start, end = end, start } if start < 1 { return newInvalidRowNumberError(start) } if end > TotalRows { return ErrMaxRows } s, err := f.stylesReader() if err != nil { return err } s.mu.Lock() defer s.mu.Unlock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { return newInvalidStyleID(styleID) } ws, err := f.workSheetReader(sheet) if err != nil { return err } ws.prepareSheetXML(0, end) for row := start - 1; row < end; row++ { ws.SheetData.Row[row].S = styleID ws.SheetData.Row[row].CustomFormat = true for i := range ws.SheetData.Row[row].C { if _, rowNum, err := CellNameToCoordinates(ws.SheetData.Row[row].C[i].R); err == nil && rowNum-1 == row { ws.SheetData.Row[row].C[i].S = styleID } } } return nil } // convertRowHeightToPixels provides a function to convert the height of a // cell from user's units to pixels. If the height hasn't been set by the user // we use the default value. If the row is hidden it has a value of zero. func convertRowHeightToPixels(height float64) float64 { if height == 0 { return 0 } return math.Ceil(4.0 / 3.4 * height) } ================================================ FILE: rows_test.go ================================================ package excelize import ( "bytes" "encoding/xml" "fmt" "path/filepath" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetRows(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1")) // Test get rows with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err := f.GetRows("Sheet1") assert.NoError(t, err) } func TestRows(t *testing.T) { const sheet2 = "Sheet2" f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test get rows with invalid sheet name _, err = f.Rows("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) rows, err := f.Rows(sheet2) assert.NoError(t, err) var collectedRows [][]string for rows.Next() { columns, err := rows.Columns() assert.NoError(t, err) collectedRows = append(collectedRows, trimSliceSpace(columns)) } if !assert.NoError(t, rows.Error()) { t.FailNow() } assert.NoError(t, rows.Close()) returnedRows, err := f.GetRows(sheet2) assert.NoError(t, err) for i := range returnedRows { returnedRows[i] = trimSliceSpace(returnedRows[i]) } if !assert.Equal(t, collectedRows, returnedRows) { t.FailNow() } assert.NoError(t, f.Close()) f.Pkg.Store("xl/worksheets/sheet1.xml", nil) _, err = f.Rows("Sheet1") assert.NoError(t, err) // Test reload the file to memory from system temporary directory f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) value, err := f.GetCellValue("Sheet1", "A19") assert.NoError(t, err) assert.Equal(t, "Total:", value) // Test load shared string table to memory err = f.SetCellValue("Sheet1", "A19", "A19") assert.NoError(t, err) value, err = f.GetCellValue("Sheet1", "A19") assert.NoError(t, err) assert.Equal(t, "A19", value) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRow.xlsx"))) assert.NoError(t, f.Close()) // Test rows iterator with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) rows, err = f.Rows(sheet2) assert.NoError(t, err) _, err = rows.Columns() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestRowsIterator(t *testing.T) { sheetName, rowCount, expectedNumRow := "Sheet2", 0, 11 f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) require.NoError(t, err) rows, err := f.Rows(sheetName) require.NoError(t, err) for rows.Next() { rowCount++ require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected") } assert.Equal(t, expectedNumRow, rowCount) assert.NoError(t, rows.Close()) assert.NoError(t, f.Close()) // Valued cell sparse distribution test f, sheetName, rowCount, expectedNumRow = NewFile(), "Sheet1", 0, 3 cells := []string{"C1", "E1", "A3", "B3", "C3", "D3", "E3"} for _, cell := range cells { assert.NoError(t, f.SetCellValue(sheetName, cell, 1)) } rows, err = f.Rows(sheetName) require.NoError(t, err) for rows.Next() { rowCount++ require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected") } assert.Equal(t, expectedNumRow, rowCount) } func TestRowsGetRowOpts(t *testing.T) { sheetName := "Sheet2" expectedRowStyleID1 := RowOpts{Height: 17.0, Hidden: false, StyleID: 1} expectedRowStyleID2 := RowOpts{Height: 17.0, Hidden: false, StyleID: 0} expectedRowStyleID3 := RowOpts{Height: 17.0, Hidden: false, StyleID: 2} f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) require.NoError(t, err) rows, err := f.Rows(sheetName) require.NoError(t, err) assert.Equal(t, true, rows.Next()) _, err = rows.Columns() require.NoError(t, err) rowOpts := rows.GetRowOpts() assert.Equal(t, expectedRowStyleID1, rowOpts) assert.Equal(t, true, rows.Next()) rowOpts = rows.GetRowOpts() assert.Equal(t, expectedRowStyleID2, rowOpts) assert.Equal(t, true, rows.Next()) _, err = rows.Columns() require.NoError(t, err) rowOpts = rows.GetRowOpts() assert.Equal(t, expectedRowStyleID3, rowOpts) } func TestRowsError(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } _, err = f.Rows("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) } func TestRowHeight(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) assert.EqualError(t, f.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), newInvalidRowNumberError(0).Error()) _, err := f.GetRowHeight("Sheet1", 0) assert.EqualError(t, err, newInvalidRowNumberError(0).Error()) assert.NoError(t, f.SetRowHeight(sheet1, 1, 111.0)) height, err := f.GetRowHeight(sheet1, 1) assert.NoError(t, err) assert.Equal(t, 111.0, height) // Test set row height overflow max row height limit assert.EqualError(t, f.SetRowHeight(sheet1, 4, MaxRowHeight+1), ErrMaxRowHeight.Error()) // Test get row height that rows index over exists rows height, err = f.GetRowHeight(sheet1, 5) assert.NoError(t, err) assert.Equal(t, defaultRowHeight, height) // Test get row height that rows heights haven't changed height, err = f.GetRowHeight(sheet1, 3) assert.NoError(t, err) assert.Equal(t, defaultRowHeight, height) // Test set and get row height on not exists worksheet assert.EqualError(t, f.SetRowHeight("SheetN", 1, 111.0), "sheet SheetN does not exist") _, err = f.GetRowHeight("SheetN", 3) assert.EqualError(t, err, "sheet SheetN does not exist") // Test set row height with invalid sheet name assert.EqualError(t, f.SetRowHeight("Sheet:1", 1, 10.0), ErrSheetNameInvalid.Error()) // Test get row height with invalid sheet name _, err = f.GetRowHeight("Sheet:1", 3) assert.EqualError(t, err, ErrSheetNameInvalid.Error()) // Test get row height with custom default row height assert.NoError(t, f.SetSheetProps(sheet1, &SheetPropsOptions{ DefaultRowHeight: float64Ptr(30.0), CustomHeight: boolPtr(true), })) height, err = f.GetRowHeight(sheet1, 100) assert.NoError(t, err) assert.Equal(t, 30.0, height) // Test set row height with custom default row height with prepare XML assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10")) _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet2", "A2", true)) height, err = f.GetRowHeight("Sheet2", 1) assert.NoError(t, err) assert.Equal(t, 15.0, height) err = f.SaveAs(filepath.Join("test", "TestRowHeight.xlsx")) if !assert.NoError(t, err) { t.FailNow() } assert.Equal(t, 0.0, convertColWidthToPixels(0)) } func TestColumns(t *testing.T) { f := NewFile() rows, err := f.Rows("Sheet1") assert.NoError(t, err) rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1`))) _, err = rows.Columns() assert.NoError(t, err) rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1`))) rows.curRow = 1 _, err = rows.Columns() assert.NoError(t, err) rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1B`))) assert.True(t, rows.Next()) _, err = rows.Columns() assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1B`))) _, err = rows.Columns() assert.NoError(t, err) rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1`))) assert.True(t, rows.Next()) _, err = rows.Columns() assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) // Test token is nil rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil)) _, err = rows.Columns() assert.NoError(t, err) } func TestSharedStringsReader(t *testing.T) { f := NewFile() // Test read shared string with unsupported charset f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err := f.sharedStringsReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test read shared strings with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) _, err = f.sharedStringsReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test read shared strings with unsupported charset workbook relationships f = NewFile() f.Relationships.Delete(defaultXMLPathWorkbookRels) f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset) _, err = f.sharedStringsReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestRowVisibility(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) assert.NoError(t, f.SetRowVisible("Sheet3", 2, false)) assert.NoError(t, f.SetRowVisible("Sheet3", 2, true)) visible, err := f.GetRowVisible("Sheet3", 2) assert.Equal(t, true, visible) assert.NoError(t, err) visible, err = f.GetRowVisible("Sheet3", 25) assert.Equal(t, false, visible) assert.NoError(t, err) assert.EqualError(t, f.SetRowVisible("Sheet3", 0, true), newInvalidRowNumberError(0).Error()) assert.EqualError(t, f.SetRowVisible("SheetN", 2, false), "sheet SheetN does not exist") // Test set row visibility with invalid sheet name assert.EqualError(t, f.SetRowVisible("Sheet:1", 1, false), ErrSheetNameInvalid.Error()) visible, err = f.GetRowVisible("Sheet3", 0) assert.Equal(t, false, visible) assert.EqualError(t, err, newInvalidRowNumberError(0).Error()) _, err = f.GetRowVisible("SheetN", 1) assert.EqualError(t, err, "sheet SheetN does not exist") // Test get row visibility with invalid sheet name _, err = f.GetRowVisible("Sheet:1", 1) assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx"))) } func TestRemoveRow(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) const ( colCount = 10 rowCount = 10 ) assert.NoError(t, fillCells(f, sheet1, colCount, rowCount)) assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External")) assert.EqualError(t, f.RemoveRow(sheet1, -1), newInvalidRowNumberError(-1).Error()) assert.EqualError(t, f.RemoveRow(sheet1, 0), newInvalidRowNumberError(0).Error()) assert.NoError(t, f.RemoveRow(sheet1, 4)) assert.Len(t, r.SheetData.Row, rowCount-1) assert.NoError(t, f.MergeCell(sheet1, "B3", "B5")) assert.NoError(t, f.RemoveRow(sheet1, 2)) assert.Len(t, r.SheetData.Row, rowCount-2) assert.NoError(t, f.RemoveRow(sheet1, 4)) assert.Len(t, r.SheetData.Row, rowCount-3) err = f.AutoFilter(sheet1, "A2:A2", []AutoFilterOptions{{Column: "A", Expression: "x != blanks"}}) assert.NoError(t, err) assert.NoError(t, f.RemoveRow(sheet1, 1)) assert.Len(t, r.SheetData.Row, rowCount-4) assert.NoError(t, f.RemoveRow(sheet1, 2)) assert.Len(t, r.SheetData.Row, rowCount-5) assert.NoError(t, f.RemoveRow(sheet1, 1)) assert.Len(t, r.SheetData.Row, rowCount-6) assert.NoError(t, f.RemoveRow(sheet1, 10)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx"))) f = NewFile() assert.NoError(t, f.MergeCell("Sheet1", "A1", "C1")) assert.NoError(t, f.MergeCell("Sheet1", "A2", "C2")) assert.NoError(t, f.RemoveRow("Sheet1", 1)) mergedCells, err := f.GetMergeCells("Sheet1") assert.NoError(t, err) assert.Equal(t, "A1", mergedCells[0].GetStartAxis()) assert.Equal(t, "C1", mergedCells[0].GetEndAxis()) // Test remove row on not exist worksheet assert.EqualError(t, f.RemoveRow("SheetN", 1), "sheet SheetN does not exist") // Test remove row with invalid sheet name assert.EqualError(t, f.RemoveRow("Sheet:1", 1), ErrSheetNameInvalid.Error()) f = NewFile() formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.RemoveRow("Sheet1", 1), "XML syntax error on line 1: invalid UTF-8") } func TestInsertRows(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) const ( colCount = 10 rowCount = 10 ) assert.NoError(t, fillCells(f, sheet1, colCount, rowCount)) assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External")) assert.NoError(t, f.InsertRows(sheet1, 1, 1)) assert.Len(t, r.SheetData.Row, rowCount+1) assert.NoError(t, f.InsertRows(sheet1, 4, 1)) assert.Len(t, r.SheetData.Row, rowCount+2) assert.NoError(t, f.InsertRows(sheet1, 4, 2)) assert.Len(t, r.SheetData.Row, rowCount+4) // Test insert rows with invalid sheet name assert.EqualError(t, f.InsertRows("Sheet:1", 1, 1), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.InsertRows(sheet1, -1, 1), newInvalidRowNumberError(-1).Error()) assert.EqualError(t, f.InsertRows(sheet1, 0, 1), newInvalidRowNumberError(0).Error()) assert.EqualError(t, f.InsertRows(sheet1, 4, 0), ErrParameterInvalid.Error()) assert.EqualError(t, f.InsertRows(sheet1, 4, TotalRows), ErrMaxRows.Error()) assert.EqualError(t, f.InsertRows(sheet1, 4, TotalRows-5), ErrMaxRows.Error()) assert.EqualError(t, f.InsertRows(sheet1, TotalRows, 1), ErrMaxRows.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRows.xlsx"))) } // Test internal structure state after insert operations. It is important // for insert workflow to be constant to avoid side effect with functions // related to internal structure. func TestInsertRowsInEmptyFile(t *testing.T) { f := NewFile() sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) assert.NoError(t, f.InsertRows(sheet1, 1, 1)) assert.Len(t, r.SheetData.Row, 0) assert.NoError(t, f.InsertRows(sheet1, 2, 1)) assert.Len(t, r.SheetData.Row, 0) assert.NoError(t, f.InsertRows(sheet1, 99, 1)) assert.Len(t, r.SheetData.Row, 0) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx"))) } func prepareTestBook2() (*File, error) { f := NewFile() for cell, val := range map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } { if err := f.SetCellStr("Sheet1", cell, val); err != nil { return f, err } } return f, nil } func TestDuplicateRowFromSingleRow(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("FromSingleRow", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"])) assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"])) assert.NoError(t, f.DuplicateRow(sheet, 1)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_1"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A1"], "B2": cells["B1"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } assert.NoError(t, f.DuplicateRow(sheet, 2)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_2"))) { t.FailNow() } expect = map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A1"], "B2": cells["B1"], "A3": cells["A1"], "B3": cells["B1"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("UpdateDuplicatedRows", func(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"])) assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"])) assert.NoError(t, f.DuplicateRow(sheet, 1)) assert.NoError(t, f.SetCellStr(sheet, "A2", cells["A2"])) assert.NoError(t, f.SetCellStr(sheet, "B2", cells["B2"])) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "UpdateDuplicatedRows"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A2"], "B2": cells["B2"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowFirstOfMultipleRows(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("FirstOfMultipleRows", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.DuplicateRow(sheet, 1)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A1"], "B2": cells["B1"], "A3": cells["A2"], "B3": cells["B2"], "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowZeroWithNoRows(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") t.Run("ZeroWithNoRows", func(t *testing.T) { f := NewFile() assert.EqualError(t, f.DuplicateRow(sheet, 0), newInvalidRowNumberError(0).Error()) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "ZeroWithNoRows"))) { t.FailNow() } val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) assert.Empty(t, val) val, err = f.GetCellValue(sheet, "B1") assert.NoError(t, err) assert.Empty(t, val) val, err = f.GetCellValue(sheet, "A2") assert.NoError(t, err) assert.Empty(t, val) val, err = f.GetCellValue(sheet, "B2") assert.NoError(t, err) assert.Empty(t, val) assert.NoError(t, err) expect := map[string]string{ "A1": "", "B1": "", "A2": "", "B2": "", } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowMiddleRowOfEmptyFile(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") t.Run("MiddleRowOfEmptyFile", func(t *testing.T) { f := NewFile() assert.NoError(t, f.DuplicateRow(sheet, 99)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "MiddleRowOfEmptyFile"))) { t.FailNow() } expect := map[string]string{ "A98": "", "A99": "", "A100": "", } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A2"], "B2": cells["B2"], "A3": cells["A1"], "B3": cells["B1"], "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A1"], "B1": cells["B1"], "A2": cells["A2"], "B2": cells["B2"], "A3": cells["A3"], "B3": cells["B3"], "A7": cells["A1"], "B7": cells["B1"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowInsertBefore(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("InsertBefore", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1)) assert.NoError(t, f.DuplicateRowTo(sheet, 10, 4)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBefore"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A2"], "B1": cells["B2"], "A2": cells["A1"], "B2": cells["B1"], "A3": cells["A2"], "B3": cells["B2"], "A5": cells["A3"], "B5": cells["B3"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v, cell) { t.FailNow() } } }) } func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) { t.FailNow() } expect := map[string]string{ "A1": cells["A3"], "B1": cells["B3"], "A2": cells["A1"], "B2": cells["B1"], "A3": cells["A2"], "B3": cells["B2"], "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { v, err := f.GetCellValue(sheet, cell) assert.NoError(t, err) if !assert.Equal(t, val, v) { t.FailNow() } } }) } func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) { f, err := prepareTestBook2() assert.NoError(t, err) assert.NoError(t, f.MergeCell(sheet, "B2", "C2")) assert.NoError(t, f.MergeCell(sheet, "C6", "C8")) assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1)) assert.NoError(t, f.DuplicateRowTo(sheet, 1, 8)) if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithMergeCells"))) { t.FailNow() } expect := []MergeCell{ {"B3:C3", "B2 Value"}, {"C7:C10", ""}, {"B1:C1", "B2 Value"}, } mergeCells, err := f.GetMergeCells(sheet) assert.NoError(t, err) for idx, val := range expect { if !assert.Equal(t, val, mergeCells[idx]) { t.FailNow() } } }) } func TestDuplicateRowInvalidRowNum(t *testing.T) { const sheet = "Sheet1" outFile := filepath.Join("test", "TestDuplicateRow.InvalidRowNum.%s.xlsx") cells := map[string]string{ "A1": "A1 Value", "A2": "A2 Value", "A3": "A3 Value", "B1": "B1 Value", "B2": "B2 Value", "B3": "B3 Value", } invalidIndexes := []int{-100, -2, -1, 0} for _, row := range invalidIndexes { name := fmt.Sprintf("%d", row) t.Run(name, func(t *testing.T) { f := NewFile() for col, val := range cells { assert.NoError(t, f.SetCellStr(sheet, col, val)) } assert.EqualError(t, f.DuplicateRow(sheet, row), newInvalidRowNumberError(row).Error()) for col, val := range cells { v, err := f.GetCellValue(sheet, col) assert.NoError(t, err) if !assert.Equal(t, val, v) { t.FailNow() } } assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name))) }) } for _, row1 := range invalidIndexes { for _, row2 := range invalidIndexes { name := fmt.Sprintf("[%d,%d]", row1, row2) t.Run(name, func(t *testing.T) { f := NewFile() for col, val := range cells { assert.NoError(t, f.SetCellStr(sheet, col, val)) } assert.EqualError(t, f.DuplicateRowTo(sheet, row1, row2), newInvalidRowNumberError(row1).Error()) for col, val := range cells { v, err := f.GetCellValue(sheet, col) assert.NoError(t, err) if !assert.Equal(t, val, v) { t.FailNow() } } assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name))) }) } } } func TestDuplicateRow(t *testing.T) { f := NewFile() // Test duplicate row with invalid sheet name assert.EqualError(t, f.DuplicateRowTo("Sheet:1", 1, 2), ErrSheetNameInvalid.Error()) f = NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$B$1", })) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "Amount+C1")) assert.NoError(t, f.SetCellValue("Sheet1", "A10", "A10")) format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) expected := []ConditionalFormatOptions{ {Type: "cell", Criteria: "greater than", Format: &format, Value: "0"}, } assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1", expected)) dv := NewDataValidation(true) dv.Sqref = "A1" assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"})) assert.NoError(t, f.AddDataValidation("Sheet1", dv)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1" assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) formula, err := f.GetCellFormula("Sheet1", "A10") assert.NoError(t, err) assert.Equal(t, "Amount+C10", formula) value, err := f.GetCellValue("Sheet1", "A11") assert.NoError(t, err) assert.Equal(t, "A10", value) cfs, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, cfs, 2) assert.Equal(t, expected, cfs["A10:A10"]) dvs, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Len(t, dvs, 2) assert.Equal(t, "A10:A10", dvs[1].Sqref) // Test duplicate data validation with row number exceeds maximum limit assert.Equal(t, ErrMaxRows, f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1)) // Test duplicate data validation with invalid range reference ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A" assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, 10)) // Test duplicate conditional formatting with row number exceeds maximum limit assert.Equal(t, ErrMaxRows, f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1)) // Test duplicate conditional formatting with invalid range reference ws.(*xlsxWorksheet).ConditionalFormatting[0].SQRef = "A" assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, 10)) } func TestDuplicateRowTo(t *testing.T) { f, sheetName := NewFile(), "Sheet1" // Test duplicate row with invalid target row number assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 0)) // Test duplicate row with equal source and target row number assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 1)) // Test duplicate row on the blank worksheet assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 2)) // Test duplicate row on the worksheet with illegal cell reference f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, }) assert.EqualError(t, f.DuplicateRowTo(sheetName, 1, 2), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test duplicate row on not exists worksheet assert.EqualError(t, f.DuplicateRowTo("SheetN", 1, 2), "sheet SheetN does not exist") // Test duplicate row with invalid sheet name assert.EqualError(t, f.DuplicateRowTo("Sheet:1", 1, 2), ErrSheetNameInvalid.Error()) } func TestDuplicateMergeCells(t *testing.T) { f := File{} ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{{Ref: "A1:-"}}, }} assert.EqualError(t, f.duplicateMergeCells(ws, "Sheet1", 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`) ws.MergeCells.Cells[0].Ref = "A1:B1" assert.EqualError(t, f.duplicateMergeCells(ws, "SheetN", 1, 2), "sheet SheetN does not exist") } func TestGetValueFromInlineStr(t *testing.T) { c := &xlsxC{T: "inlineStr"} f := NewFile() d := &xlsxSST{} val, err := c.getValueFrom(f, d, false) assert.NoError(t, err) assert.Empty(t, val) } func TestGetValueFromNumber(t *testing.T) { c := &xlsxC{T: "n"} f := NewFile() d := &xlsxSST{} for input, expected := range map[string]string{ "2.2.": "2.2.", "1.1000000000000001": "1.1", "2.2200000000000002": "2.22", "28.552": "28.552", "27.399000000000001": "27.399", "26.245999999999999": "26.246", "2422.3000000000002": "2422.3", "2.220000ddsf0000000002-r": "2.220000ddsf0000000002-r", } { c.V = input val, err := c.getValueFrom(f, d, false) assert.NoError(t, err) assert.Equal(t, expected, val) } } func TestErrSheetNotExistError(t *testing.T) { assert.Equal(t, "sheet Sheet1 does not exist", ErrSheetNotExist{"Sheet1"}.Error()) } func TestCheckRow(t *testing.T) { f := NewFile() f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) _, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", false)) f = NewFile() f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.checked.Delete("xl/worksheets/sheet1.xml") assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), newCellNameToCoordinatesError("-", newInvalidCellNameError("-")).Error()) } func TestSetRowStyle(t *testing.T) { f := NewFile() style1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"63BE7B"}, Pattern: 1}}) assert.NoError(t, err) style2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1)) assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error()) assert.EqualError(t, f.SetRowStyle("Sheet1", 1, TotalRows+1, style2), ErrMaxRows.Error()) // Test set row style with invalid style ID assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, -1), newInvalidStyleID(-1).Error()) // Test set row style with not exists style ID assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, 10), newInvalidStyleID(10).Error()) assert.EqualError(t, f.SetRowStyle("SheetN", 1, 1, style2), "sheet SheetN does not exist") // Test set row style with invalid sheet name assert.EqualError(t, f.SetRowStyle("Sheet:1", 1, 1, 0), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SetRowStyle("Sheet1", 5, 1, style2)) cellStyleID, err := f.GetCellStyle("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, style2, cellStyleID) // Test cell inheritance rows style assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil)) cellStyleID, err = f.GetCellStyle("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, style2, cellStyleID) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx"))) // Test set row style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, cellStyleID), "XML syntax error on line 1: invalid UTF-8") } func TestSetRowHeight(t *testing.T) { f := NewFile() // Test hidden row by set row height to 0 assert.NoError(t, f.SetRowHeight("Sheet1", 2, 0)) ht, err := f.GetRowHeight("Sheet1", 2) assert.NoError(t, err) assert.Empty(t, ht) // Test unset custom row height assert.NoError(t, f.SetRowHeight("Sheet1", 2, -1)) ht, err = f.GetRowHeight("Sheet1", 2) assert.NoError(t, err) assert.Equal(t, defaultRowHeight, ht) // Test set row height with invalid height value assert.Equal(t, ErrParameterInvalid, f.SetRowHeight("Sheet1", 2, -2)) } func TestNumberFormats(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } cells := make([][]string, 0) cols, err := f.Cols("Sheet2") if !assert.NoError(t, err) { t.FailNow() } for cols.Next() { col, err := cols.Rows() assert.NoError(t, err) if err != nil { break } cells = append(cells, col) } assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3]) assert.NoError(t, f.Close()) f = NewFile() numFmt1, err := f.NewStyle(&Style{NumFmt: 1}) assert.NoError(t, err) numFmt2, err := f.NewStyle(&Style{NumFmt: 2}) assert.NoError(t, err) numFmt3, err := f.NewStyle(&Style{NumFmt: 3}) assert.NoError(t, err) numFmt9, err := f.NewStyle(&Style{NumFmt: 9}) assert.NoError(t, err) numFmt10, err := f.NewStyle(&Style{NumFmt: 10}) assert.NoError(t, err) numFmt21, err := f.NewStyle(&Style{NumFmt: 21}) assert.NoError(t, err) numFmt37, err := f.NewStyle(&Style{NumFmt: 37}) assert.NoError(t, err) numFmt38, err := f.NewStyle(&Style{NumFmt: 38}) assert.NoError(t, err) numFmt39, err := f.NewStyle(&Style{NumFmt: 39}) assert.NoError(t, err) numFmt40, err := f.NewStyle(&Style{NumFmt: 40}) assert.NoError(t, err) for _, cases := range [][]interface{}{ {"A1", numFmt1, 8.8888666665555493e+19, "88888666665555500000"}, {"A2", numFmt1, 8.8888666665555487, "9"}, {"A3", numFmt2, 8.8888666665555493e+19, "88888666665555500000.00"}, {"A4", numFmt2, 8.8888666665555487, "8.89"}, {"A5", numFmt3, 8.8888666665555493e+19, "88,888,666,665,555,500,000"}, {"A6", numFmt3, 8.8888666665555487, "9"}, {"A7", numFmt3, 123, "123"}, {"A8", numFmt3, -1234, "-1,234"}, {"A9", numFmt9, 8.8888666665555493e+19, "8888866666555550000000%"}, {"A10", numFmt9, -8.8888666665555493e+19, "-8888866666555550000000%"}, {"A11", numFmt9, 8.8888666665555487, "889%"}, {"A12", numFmt9, -8.8888666665555487, "-889%"}, {"A13", numFmt10, 8.8888666665555493e+19, "8888866666555550000000.00%"}, {"A14", numFmt10, -8.8888666665555493e+19, "-8888866666555550000000.00%"}, {"A15", numFmt10, 8.8888666665555487, "888.89%"}, {"A16", numFmt10, -8.8888666665555487, "-888.89%"}, {"A17", numFmt37, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "}, {"A18", numFmt37, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"}, {"A19", numFmt37, 8.8888666665555487, "9 "}, {"A20", numFmt37, -8.8888666665555487, "(9)"}, {"A21", numFmt38, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "}, {"A22", numFmt38, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"}, {"A23", numFmt38, 8.8888666665555487, "9 "}, {"A24", numFmt38, -8.8888666665555487, "(9)"}, {"A25", numFmt39, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "}, {"A26", numFmt39, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"}, {"A27", numFmt39, 8.8888666665555487, "8.89 "}, {"A28", numFmt39, -8.8888666665555487, "(8.89)"}, {"A29", numFmt40, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "}, {"A30", numFmt40, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"}, {"A31", numFmt40, 8.8888666665555487, "8.89 "}, {"A32", numFmt40, -8.8888666665555487, "(8.89)"}, {"A33", numFmt21, 44729.999988368058, "23:59:59"}, {"A34", numFmt21, 44944.375005787035, "09:00:00"}, {"A35", numFmt21, 44944.375005798611, "09:00:01"}, } { cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string) assert.NoError(t, f.SetCellStyle("Sheet1", cell, cell, styleID)) assert.NoError(t, f.SetCellValue("Sheet1", cell, value)) result, err := f.GetCellValue("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, result, cell) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx"))) f = NewFile(Options{ShortDatePattern: "yyyy/m/d"}) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 43543.503206018519)) numFmt14, err := f.NewStyle(&Style{NumFmt: 14}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", numFmt14)) result, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "2019/3/19", result, "A1") } func TestCellXMLHandler(t *testing.T) { var ( content = []byte(fmt.Sprintf(`10String2*A10A32422.30000000000022022-10-22T15:05:29Z`, NameSpaceSpreadSheet.Value)) expected, ws xlsxWorksheet row *xlsxRow ) assert.NoError(t, xml.Unmarshal(content, &expected)) decoder := xml.NewDecoder(bytes.NewReader(content)) rows := Rows{decoder: decoder} for { token, _ := decoder.Token() if token == nil { break } switch element := token.(type) { case xml.StartElement: if element.Name.Local == "row" { r, err := strconv.Atoi(element.Attr[0].Value) assert.NoError(t, err) ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: r}) row = &ws.SheetData.Row[len(ws.SheetData.Row)-1] } if element.Name.Local == "c" { colCell := xlsxC{} assert.NoError(t, colCell.cellXMLHandler(rows.decoder, &element)) row.C = append(row.C, colCell) } } } assert.Equal(t, expected.SheetData.Row, ws.SheetData.Row) for _, rowXML := range []string{ `10`, // s need number `10 `, // missing ``, // incorrect data } { ws := xlsxWorksheet{} content := []byte(fmt.Sprintf(`%s`, NameSpaceSpreadSheet.Value, rowXML)) expected := xml.Unmarshal(content, &ws) assert.Error(t, expected) decoder := xml.NewDecoder(bytes.NewReader(content)) rows := Rows{decoder: decoder} for { token, _ := decoder.Token() if token == nil { break } switch element := token.(type) { case xml.StartElement: if element.Name.Local == "c" { colCell := xlsxC{} err := colCell.cellXMLHandler(rows.decoder, &element) assert.Error(t, err) assert.Equal(t, expected, err) } } } } } func BenchmarkRows(b *testing.B) { f, _ := OpenFile(filepath.Join("test", "Book1.xlsx")) for i := 0; i < b.N; i++ { rows, _ := f.Rows("Sheet2") for rows.Next() { row, _ := rows.Columns() for i := range row { if i >= 0 { continue } } } if err := rows.Close(); err != nil { b.Error(err) } } if err := f.Close(); err != nil { b.Error(err) } } // trimSliceSpace trim continually blank element in the tail of slice. func trimSliceSpace(s []string) []string { for { if len(s) > 0 && s[len(s)-1] == "" { s = s[:len(s)-1] } else { break } } return s } ================================================ FILE: shape.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "strconv" "strings" ) // parseShapeOptions provides a function to parse the format settings of the // shape with default value. func parseShapeOptions(opts *Shape) (*Shape, error) { if opts == nil { return nil, ErrParameterInvalid } if opts.Type == "" { return nil, ErrParameterInvalid } if opts.Width == 0 { opts.Width = defaultShapeSize } if opts.Height == 0 { opts.Height = defaultShapeSize } if opts.Line.Width == nil { opts.Line.Width = float64Ptr(defaultShapeLineWidth) } if opts.Fill.Transparency < 0 || 100 < opts.Fill.Transparency { return opts, ErrTransparency } format := opts.Format graphicOptions, err := format.parseGraphicOptions(nil) if err != nil { return opts, err } opts.Format = *graphicOptions return opts, err } // AddShape provides the method to add shape in a sheet by given worksheet // name and shape format set (such as offset, scale, aspect ratio setting and // print settings). For example, add text box (rect shape) in Sheet1: // // lineWidth := 1.2 // err := f.AddShape("Sheet1", // &excelize.Shape{ // Cell: "G6", // Type: "rect", // Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth}, // Fill: excelize.Fill{Color: []string{"8EB9FF"}, Pattern: 1}, // Paragraph: []excelize.RichTextRun{ // { // Text: "Rectangle Shape", // Font: &excelize.Font{ // Bold: true, // Italic: true, // Family: "Times New Roman", // Size: 18, // Color: "777777", // Underline: "sng", // }, // }, // }, // Width: 180, // Height: 40, // }, // ) // // The following shows the type of shape supported by excelize: // // accentBorderCallout1 (Callout 1 with Border and Accent Shape) // accentBorderCallout2 (Callout 2 with Border and Accent Shape) // accentBorderCallout3 (Callout 3 with Border and Accent Shape) // accentCallout1 (Callout 1 Shape) // accentCallout2 (Callout 2 Shape) // accentCallout3 (Callout 3 Shape) // actionButtonBackPrevious (Back or Previous Button Shape) // actionButtonBeginning (Beginning Button Shape) // actionButtonBlank (Blank Button Shape) // actionButtonDocument (Document Button Shape) // actionButtonEnd (End Button Shape) // actionButtonForwardNext (Forward or Next Button Shape) // actionButtonHelp (Help Button Shape) // actionButtonHome (Home Button Shape) // actionButtonInformation (Information Button Shape) // actionButtonMovie (Movie Button Shape) // actionButtonReturn (Return Button Shape) // actionButtonSound (Sound Button Shape) // arc (Curved Arc Shape) // bentArrow (Bent Arrow Shape) // bentConnector2 (Bent Connector 2 Shape) // bentConnector3 (Bent Connector 3 Shape) // bentConnector4 (Bent Connector 4 Shape) // bentConnector5 (Bent Connector 5 Shape) // bentUpArrow (Bent Up Arrow Shape) // bevel (Bevel Shape) // blockArc (Block Arc Shape) // borderCallout1 (Callout 1 with Border Shape) // borderCallout2 (Callout 2 with Border Shape) // borderCallout3 (Callout 3 with Border Shape) // bracePair (Brace Pair Shape) // bracketPair (Bracket Pair Shape) // callout1 (Callout 1 Shape) // callout2 (Callout 2 Shape) // callout3 (Callout 3 Shape) // can (Can Shape) // chartPlus (Chart Plus Shape) // chartStar (Chart Star Shape) // chartX (Chart X Shape) // chevron (Chevron Shape) // chord (Chord Shape) // circularArrow (Circular Arrow Shape) // cloud (Cloud Shape) // cloudCallout (Callout Cloud Shape) // corner (Corner Shape) // cornerTabs (Corner Tabs Shape) // cube (Cube Shape) // curvedConnector2 (Curved Connector 2 Shape) // curvedConnector3 (Curved Connector 3 Shape) // curvedConnector4 (Curved Connector 4 Shape) // curvedConnector5 (Curved Connector 5 Shape) // curvedDownArrow (Curved Down Arrow Shape) // curvedLeftArrow (Curved Left Arrow Shape) // curvedRightArrow (Curved Right Arrow Shape) // curvedUpArrow (Curved Up Arrow Shape) // decagon (Decagon Shape) // diagStripe (Diagonal Stripe Shape) // diamond (Diamond Shape) // dodecagon (Dodecagon Shape) // donut (Donut Shape) // doubleWave (Double Wave Shape) // downArrow (Down Arrow Shape) // downArrowCallout (Callout Down Arrow Shape) // ellipse (Ellipse Shape) // ellipseRibbon (Ellipse Ribbon Shape) // ellipseRibbon2 (Ellipse Ribbon 2 Shape) // flowChartAlternateProcess (Alternate Process Flow Shape) // flowChartCollate (Collate Flow Shape) // flowChartConnector (Connector Flow Shape) // flowChartDecision (Decision Flow Shape) // flowChartDelay (Delay Flow Shape) // flowChartDisplay (Display Flow Shape) // flowChartDocument (Document Flow Shape) // flowChartExtract (Extract Flow Shape) // flowChartInputOutput (Input Output Flow Shape) // flowChartInternalStorage (Internal Storage Flow Shape) // flowChartMagneticDisk (Magnetic Disk Flow Shape) // flowChartMagneticDrum (Magnetic Drum Flow Shape) // flowChartMagneticTape (Magnetic Tape Flow Shape) // flowChartManualInput (Manual Input Flow Shape) // flowChartManualOperation (Manual Operation Flow Shape) // flowChartMerge (Merge Flow Shape) // flowChartMultidocument (Multi-Document Flow Shape) // flowChartOfflineStorage (Offline Storage Flow Shape) // flowChartOffpageConnector (Off-Page Connector Flow Shape) // flowChartOnlineStorage (Online Storage Flow Shape) // flowChartOr (Or Flow Shape) // flowChartPredefinedProcess (Predefined Process Flow Shape) // flowChartPreparation (Preparation Flow Shape) // flowChartProcess (Process Flow Shape) // flowChartPunchedCard (Punched Card Flow Shape) // flowChartPunchedTape (Punched Tape Flow Shape) // flowChartSort (Sort Flow Shape) // flowChartSummingJunction (Summing Junction Flow Shape) // flowChartTerminator (Terminator Flow Shape) // foldedCorner (Folded Corner Shape) // frame (Frame Shape) // funnel (Funnel Shape) // gear6 (Gear 6 Shape) // gear9 (Gear 9 Shape) // halfFrame (Half Frame Shape) // heart (Heart Shape) // heptagon (Heptagon Shape) // hexagon (Hexagon Shape) // homePlate (Home Plate Shape) // horizontalScroll (Horizontal Scroll Shape) // irregularSeal1 (Irregular Seal 1 Shape) // irregularSeal2 (Irregular Seal 2 Shape) // leftArrow (Left Arrow Shape) // leftArrowCallout (Callout Left Arrow Shape) // leftBrace (Left Brace Shape) // leftBracket (Left Bracket Shape) // leftCircularArrow (Left Circular Arrow Shape) // leftRightArrow (Left Right Arrow Shape) // leftRightArrowCallout (Callout Left Right Arrow Shape) // leftRightCircularArrow (Left Right Circular Arrow Shape) // leftRightRibbon (Left Right Ribbon Shape) // leftRightUpArrow (Left Right Up Arrow Shape) // leftUpArrow (Left Up Arrow Shape) // lightningBolt (Lightning Bolt Shape) // line (Line Shape) // lineInv (Line Inverse Shape) // mathDivide (Divide Math Shape) // mathEqual (Equal Math Shape) // mathMinus (Minus Math Shape) // mathMultiply (Multiply Math Shape) // mathNotEqual (Not Equal Math Shape) // mathPlus (Plus Math Shape) // moon (Moon Shape) // nonIsoscelesTrapezoid (Non-Isosceles Trapezoid Shape) // noSmoking (No Smoking Shape) // notchedRightArrow (Notched Right Arrow Shape) // octagon (Octagon Shape) // parallelogram (Parallelogram Shape) // pentagon (Pentagon Shape) // pie (Pie Shape) // pieWedge (Pie Wedge Shape) // plaque (Plaque Shape) // plaqueTabs (Plaque Tabs Shape) // plus (Plus Shape) // quadArrow (Quad-Arrow Shape) // quadArrowCallout (Callout Quad-Arrow Shape) // rect (Rectangle Shape) // ribbon (Ribbon Shape) // ribbon2 (Ribbon 2 Shape) // rightArrow (Right Arrow Shape) // rightArrowCallout (Callout Right Arrow Shape) // rightBrace (Right Brace Shape) // rightBracket (Right Bracket Shape) // round1Rect (One Round Corner Rectangle Shape) // round2DiagRect (Two Diagonal Round Corner Rectangle Shape) // round2SameRect (Two Same-side Round Corner Rectangle Shape) // roundRect (Round Corner Rectangle Shape) // rtTriangle (Right Triangle Shape) // smileyFace (Smiley Face Shape) // snip1Rect (One Snip Corner Rectangle Shape) // snip2DiagRect (Two Diagonal Snip Corner Rectangle Shape) // snip2SameRect (Two Same-side Snip Corner Rectangle Shape) // snipRoundRect (One Snip One Round Corner Rectangle Shape) // squareTabs (Square Tabs Shape) // star10 (Ten Pointed Star Shape) // star12 (Twelve Pointed Star Shape) // star16 (Sixteen Pointed Star Shape) // star24 (Twenty Four Pointed Star Shape) // star32 (Thirty Two Pointed Star Shape) // star4 (Four Pointed Star Shape) // star5 (Five Pointed Star Shape) // star6 (Six Pointed Star Shape) // star7 (Seven Pointed Star Shape) // star8 (Eight Pointed Star Shape) // straightConnector1 (Straight Connector 1 Shape) // stripedRightArrow (Striped Right Arrow Shape) // sun (Sun Shape) // swooshArrow (Swoosh Arrow Shape) // teardrop (Teardrop Shape) // trapezoid (Trapezoid Shape) // triangle (Triangle Shape) // upArrow (Up Arrow Shape) // upArrowCallout (Callout Up Arrow Shape) // upDownArrow (Up Down Arrow Shape) // upDownArrowCallout (Callout Up Down Arrow Shape) // uturnArrow (U-Turn Arrow Shape) // verticalScroll (Vertical Scroll Shape) // wave (Wave Shape) // wedgeEllipseCallout (Callout Wedge Ellipse Shape) // wedgeRectCallout (Callout Wedge Rectangle Shape) // wedgeRoundRectCallout (Callout Wedge Round Rectangle Shape) // // The following shows the type of text underline supported by excelize: // // none // words // sng // dbl // heavy // dotted // dottedHeavy // dash // dashHeavy // dashLong // dashLongHeavy // dotDash // dotDashHeavy // dotDotDash // dotDotDashHeavy // wavy // wavyHeavy // wavyDbl func (f *File) AddShape(sheet string, opts *Shape) error { options, err := parseShapeOptions(opts) if err != nil { return err } // Read sheet data ws, err := f.workSheetReader(sheet) if err != nil { return err } // Add first shape for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. drawingID := f.countDrawings() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" if ws.Drawing != nil { // The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml. sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml")) drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl") } else { // Add first shape for given sheet. sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) f.addSheetNameSpace(sheet, SourceRelationship) } if err = f.addDrawingShape(sheet, drawingXML, opts.Cell, options); err != nil { return err } return f.addContentTypePart(drawingID, "drawings") } // cellAnchorShape create a two cell anchor shape size placeholder for a // group, a shape, or a drawing element. func (f *File) cellAnchorShape(sheet, drawingXML, cell string, width, height uint, format GraphicOptions) (*xlsxWsDr, *xdrCellAnchor, int, error) { fromCol, fromRow, err := CellNameToCoordinates(cell) if err != nil { return nil, nil, 0, err } w := int(float64(width) * format.ScaleX) h := int(float64(height) * format.ScaleY) colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, w, h, &format) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return content, nil, cNvPrID, err } cellAnchor := xdrCellAnchor{} from := xlsxFrom{} from.Col = colStart from.ColOff = x1 * EMU from.Row = rowStart from.RowOff = y1 * EMU cellAnchor.From = &from if format.Positioning != "oneCell" { to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU to.Row = rowEnd to.RowOff = y2 * EMU cellAnchor.To = &to cellAnchor.EditAs = format.Positioning } if format.Positioning == "oneCell" { cellAnchor.Ext = &xlsxPositiveSize2D{ Cx: x2 * EMU, Cy: y2 * EMU, } } return content, &cellAnchor, cNvPrID, err } // addDrawingShape provides a function to add preset geometry by given sheet, // drawingXML and format sets. func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error { content, cellAnchor, cNvPrID, err := f.cellAnchorShape( sheet, drawingXML, cell, opts.Width, opts.Height, opts.Format) if err != nil { return err } var solidColor string if len(opts.Fill.Color) == 1 { solidColor = strings.ReplaceAll(strings.ToUpper(opts.Fill.Color[0]), "#", "") } shape := xdrSp{ Macro: opts.Macro, NvSpPr: &xdrNvSpPr{ CNvPr: &xlsxCNvPr{ ID: cNvPrID, Name: "Shape " + strconv.Itoa(cNvPrID), Descr: opts.Format.AltText, }, CNvSpPr: &xdrCNvSpPr{ TxBox: true, }, }, SpPr: &xlsxSpPr{ Xfrm: xlsxXfrm{ Ext: xlsxPositiveSize2D{ Cx: int(opts.Width) * EMU, Cy: int(opts.Height) * EMU, }, }, PrstGeom: xlsxPrstGeom{ Prst: opts.Type, }, }, Style: &xdrStyle{ LnRef: setShapeRef(opts.Line.Color, 2), FillRef: setShapeRef(solidColor, 1), EffectRef: setShapeRef("", 0), FontRef: &aFontRef{ Idx: "minor", SchemeClr: &attrValString{ Val: stringPtr("tx1"), }, }, }, TxBody: &xdrTxBody{ BodyPr: &aBodyPr{ VertOverflow: "clip", HorzOverflow: "clip", Wrap: "none", RtlCol: false, Anchor: "t", }, }, } if len(opts.Format.Name) > 0 { shape.NvSpPr.CNvPr.Name = opts.Format.Name } if *opts.Line.Width != 1 { shape.SpPr.Ln = xlsxLineProperties{ W: f.ptToEMUs(*opts.Line.Width), } } if opts.Fill.Transparency > 0 { val := (100 - opts.Fill.Transparency) * 1000 shape.SpPr.SolidFill = &aSolidFill{SrgbClr: &aSrgbClr{ Val: stringPtr(solidColor), Alpha: &attrValInt{Val: &val}, }} } defaultFont, err := f.GetDefaultFont() if err != nil { return err } if len(opts.Paragraph) < 1 { opts.Paragraph = []RichTextRun{ { Font: &Font{ Bold: false, Italic: false, Underline: "none", Family: defaultFont, Size: 11, Color: "000000", }, Text: " ", }, } } for _, p := range opts.Paragraph { u := "none" font := &Font{} if p.Font != nil { font = p.Font } if idx := inStrSlice(supportedDrawingUnderlineTypes, font.Underline, true); idx != -1 { u = supportedDrawingUnderlineTypes[idx] } text := p.Text if text == "" { text = " " } paragraph := &aP{ R: &aR{ RPr: aRPr{ I: font.Italic, B: font.Bold, Lang: "en-US", AltLang: "en-US", U: u, Sz: font.Size * 100, Latin: &xlsxCTTextFont{Typeface: font.Family}, Ea: &xlsxCTTextFont{Typeface: font.Family}, Cs: &xlsxCTTextFont{Typeface: font.Family}, }, T: text, }, EndParaRPr: &aEndParaRPr{ Lang: "en-US", }, } srgbClr := strings.ReplaceAll(strings.ToUpper(font.Color), "#", "") if len(srgbClr) == 6 { paragraph.R.RPr.SolidFill = &aSolidFill{ SrgbClr: &aSrgbClr{ Val: stringPtr(srgbClr), }, } } shape.TxBody.P = append(shape.TxBody.P, paragraph) } cellAnchor.Sp = &shape cellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Format.Locked, FPrintsWithSheet: *opts.Format.PrintObject, } if opts.Format.Positioning == "oneCell" { content.OneCellAnchor = append(content.OneCellAnchor, cellAnchor) } else { content.TwoCellAnchor = append(content.TwoCellAnchor, cellAnchor) } f.Drawings.Store(drawingXML, content) return err } // setShapeRef provides a function to set color with hex model by given actual // color value. func setShapeRef(color string, i int) *aRef { if color == "" { return &aRef{ Idx: 0, ScrgbClr: &aScrgbClr{ R: 0, G: 0, B: 0, }, } } return &aRef{ Idx: i, SrgbClr: &attrValString{ Val: stringPtr(strings.ReplaceAll(strings.ToUpper(color), "#", "")), }, } } ================================================ FILE: shape_test.go ================================================ package excelize import ( "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestAddShape(t *testing.T) { f, err := prepareTestBook1() if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.AddShape("Sheet1", &Shape{ Cell: "A30", Type: "rect", Paragraph: []RichTextRun{ {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}}, {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}}, }, })) assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}})) shape1 := Shape{Cell: "C30", Type: "rect", Width: 160, Height: 160} assert.NoError(t, f.AddShape("Sheet1", &shape1)) // Test add shape with invalid positioning types assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning), f.AddShape("Sheet1", &Shape{Cell: "C30", Type: "rect", Format: GraphicOptions{Positioning: "x"}})) assert.EqualError(t, f.AddShape("Sheet3", &Shape{Cell: "C30", Type: "rect"}), "sheet Sheet3 does not exist") assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet3", nil)) assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet1", &Shape{Cell: "A1"})) assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddShape("Sheet1", &Shape{ Cell: "A", Type: "rect", Paragraph: []RichTextRun{ {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}}, {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}}, }, })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx"))) // Test add first shape for given sheet f = NewFile() lineWidth := 1.2 shape2 := Shape{ Cell: "A1", Type: "ellipseRibbon", Line: ShapeLine{Color: "4286F4", Width: &lineWidth}, Fill: Fill{Color: []string{"8EB9FF"}, Transparency: 60}, Format: GraphicOptions{ AltText: "Shape", Name: "Shape 1", PrintObject: boolPtr(true), Locked: boolPtr(false), ScaleX: 0.8, ScaleY: 0.8, Positioning: "oneCell", }, Paragraph: []RichTextRun{ { Font: &Font{ Bold: true, Italic: true, Family: "Times New Roman", Size: 18, Color: "777777", Underline: "sng", }, Text: "Shape", }, }, Height: 90, } assert.NoError(t, f.AddShape("Sheet1", &shape2)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) // Test add shape with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.AddShape("Sheet:1", &Shape{ Cell: "A30", Type: "rect", Paragraph: []RichTextRun{ {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}}, {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}}, }, })) // Test add shape with transparency value exceeds limit assert.Equal(t, ErrTransparency, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Fill: Fill{Color: []string{"8EB9FF"}, Transparency: 110}})) // Test add shape with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8") // Test add shape with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8") } func TestAddDrawingShape(t *testing.T) { f := NewFile() path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", &Shape{ Width: defaultShapeSize, Height: defaultShapeSize, Format: GraphicOptions{ PrintObject: boolPtr(true), Locked: boolPtr(false), }, }, ), "XML syntax error on line 1: invalid UTF-8") } ================================================ FILE: sheet.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "os" "path" "path/filepath" "reflect" "regexp" "sort" "strconv" "strings" "github.com/tiendc/go-deepcopy" ) // IgnoredErrorsType is the type of ignored errors. type IgnoredErrorsType byte // Ignored errors types enumeration. const ( IgnoredErrorsEvalError = iota IgnoredErrorsTwoDigitTextYear IgnoredErrorsNumberStoredAsText IgnoredErrorsFormula IgnoredErrorsFormulaRange IgnoredErrorsUnlockedFormula IgnoredErrorsEmptyCellReference IgnoredErrorsListDataValidation IgnoredErrorsCalculatedColumn ) // NewSheet provides the function to create a new sheet by given a worksheet // name and returns the index of the sheets in the workbook after it appended. // Note that when creating a new workbook, the default worksheet named // `Sheet1` will be created. func (f *File) NewSheet(sheet string) (int, error) { var err error if err = checkSheetName(sheet); err != nil { return -1, err } // Check if the worksheet already exists index, err := f.GetSheetIndex(sheet) if index != -1 { return index, err } _ = f.DeleteSheet(sheet) f.SheetCount++ wb, _ := f.workbookReader() sheetID := 0 for _, v := range wb.Sheets.Sheet { if v.SheetID > sheetID { sheetID = v.SheetID } } sheetID++ // Update [Content_Types].xml _ = f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet) // Create new sheet /xl/worksheets/sheet%d.xml f.setSheet(sheetID, sheet) // Update workbook.xml.rels rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("worksheets/sheet%d.xml", sheetID), "") // Update workbook.xml f.setWorkbook(sheet, sheetID, rID) return f.GetSheetIndex(sheet) } // contentTypesReader provides a function to get the pointer to the // [Content_Types].xml structure after deserialization. func (f *File) contentTypesReader() (*xlsxTypes, error) { if f.ContentTypes == nil { f.ContentTypes = new(xlsxTypes) f.ContentTypes.mu.Lock() defer f.ContentTypes.mu.Unlock() if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))). Decode(f.ContentTypes); err != nil && err != io.EOF { return f.ContentTypes, err } } return f.ContentTypes, nil } // contentTypesWriter provides a function to save [Content_Types].xml after // serialize structure. func (f *File) contentTypesWriter() { if f.ContentTypes != nil { output, _ := xml.Marshal(f.ContentTypes) f.saveFileList(defaultXMLPathContentTypes, output) } } // getWorksheetPath construct a target XML as xl/worksheets/sheet%d by split // path, compatible with different types of relative paths in // workbook.xml.rels, for example: worksheets/sheet%d.xml // and /xl/worksheets/sheet%d.xml func (f *File) getWorksheetPath(relTarget string) (path string) { path = filepath.ToSlash(strings.TrimPrefix( strings.ReplaceAll(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), relTarget)), "\\", "/"), "/")) if strings.HasPrefix(relTarget, "/") { path = filepath.ToSlash(strings.TrimPrefix(strings.ReplaceAll(filepath.Clean(relTarget), "\\", "/"), "/")) } return path } // mergeExpandedCols merge expanded columns. func (f *File) mergeExpandedCols(ws *xlsxWorksheet) { sort.Slice(ws.Cols.Col, func(i, j int) bool { return ws.Cols.Col[i].Min < ws.Cols.Col[j].Min }) var columns []xlsxCol for i, n := 0, len(ws.Cols.Col); i < n; { left := i for i++; i < n && reflect.DeepEqual( xlsxCol{ BestFit: ws.Cols.Col[i-1].BestFit, Collapsed: ws.Cols.Col[i-1].Collapsed, CustomWidth: ws.Cols.Col[i-1].CustomWidth, Hidden: ws.Cols.Col[i-1].Hidden, Max: ws.Cols.Col[i-1].Max + 1, Min: ws.Cols.Col[i-1].Min + 1, OutlineLevel: ws.Cols.Col[i-1].OutlineLevel, Phonetic: ws.Cols.Col[i-1].Phonetic, Style: ws.Cols.Col[i-1].Style, Width: ws.Cols.Col[i-1].Width, }, ws.Cols.Col[i]); i++ { } var column xlsxCol _ = deepcopy.Copy(&column, ws.Cols.Col[left]) if left < i-1 { column.Max = ws.Cols.Col[i-1].Min } columns = append(columns, column) } ws.Cols.Col = columns } // workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after // serialize structure. func (f *File) workSheetWriter() { var ( arr []byte buffer = bytes.NewBuffer(arr) encoder = xml.NewEncoder(buffer) ) f.Sheet.Range(func(p, ws interface{}) bool { if ws != nil { sheet := ws.(*xlsxWorksheet) if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 { _ = sheet.mergeOverlapCells() } if sheet.Cols != nil && len(sheet.Cols.Col) > 0 { f.mergeExpandedCols(sheet) } sheet.SheetData.Row = trimRow(&sheet.SheetData) if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil { f.addNameSpaces(p.(string), SourceRelationship) } if sheet.DecodeAlternateContent != nil { sheet.AlternateContent = &xlsxAlternateContent{ Content: sheet.DecodeAlternateContent.Content, XMLNSMC: SourceRelationshipCompatibility.Value, } } sheet.DecodeAlternateContent = nil // reusing buffer _ = encoder.Encode(sheet) f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes()))) _, ok := f.checked.Load(p.(string)) if ok { f.Sheet.Delete(p.(string)) f.checked.Delete(p.(string)) } buffer.Reset() } return true }) } // trimRow provides a function to trim empty rows. func trimRow(sheetData *xlsxSheetData) []xlsxRow { var ( row xlsxRow i int ) for k := 0; k < len(sheetData.Row); k++ { row = sheetData.Row[k] if row = trimCell(row); len(row.C) != 0 || row.hasAttr() { sheetData.Row[i] = row i++ continue } sheetData.Row = append(sheetData.Row[:k], sheetData.Row[k+1:]...) k-- } return sheetData.Row[:i] } // trimCell provides a function to trim blank cells which created by fillColumns. func trimCell(row xlsxRow) xlsxRow { column := row.C rowFull := true for i := range column { rowFull = column[i].hasValue() && rowFull } if rowFull { return row } i := 0 for _, c := range column { if c.hasValue() { row.C[i] = c i++ } } row.C = row.C[:i] return row } // setContentTypes provides a function to read and update property of contents // type of the spreadsheet. func (f *File) setContentTypes(partName, contentType string) error { content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() content.Overrides = append(content.Overrides, xlsxOverride{ PartName: partName, ContentType: contentType, }) return err } // setSheet provides a function to update sheet property by given index. func (f *File) setSheet(index int, name string) { ws := xlsxWorksheet{ Dimension: &xlsxDimension{Ref: "A1"}, SheetViews: &xlsxSheetViews{ SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, }, } sheetXMLPath := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml" f.sheetMap[name] = sheetXMLPath f.Sheet.Store(sheetXMLPath, &ws) f.xmlAttr.Store(sheetXMLPath, []xml.Attr{NameSpaceSpreadSheet}) } // relsWriter provides a function to save relationships after // serialize structure. func (f *File) relsWriter() { f.Relationships.Range(func(path, rel interface{}) bool { if rel != nil { output, _ := xml.Marshal(rel.(*xlsxRelationships)) if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") { output = f.replaceNameSpaceBytes(path.(string), output) } f.saveFileList(path.(string), replaceRelationshipsBytes(output)) } return true }) } // replaceRelationshipsBytes; Some tools that read spreadsheet files have very // strict requirements about the structure of the input XML. This function is // a horrible hack to fix that after the XML marshalling is completed. func replaceRelationshipsBytes(content []byte) []byte { sourceXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`) targetXmlns := []byte("r") return bytesReplace(content, sourceXmlns, targetXmlns, -1) } // SetActiveSheet provides a function to set the default active sheet of the // workbook by a given index. Note that the active index is different from the // ID returned by function GetSheetMap(). It should be greater than or equal to 0 // and less than the total worksheet numbers. func (f *File) SetActiveSheet(index int) { if index < 0 { index = 0 } wb, _ := f.workbookReader() for activeTab := range wb.Sheets.Sheet { if activeTab == index { if wb.BookViews == nil { wb.BookViews = &xlsxBookViews{} } if len(wb.BookViews.WorkBookView) > 0 { wb.BookViews.WorkBookView[0].ActiveTab = activeTab } else { wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{ ActiveTab: activeTab, }) } } } for idx, name := range f.GetSheetList() { ws, err := f.workSheetReader(name) if err != nil { // Chartsheet, macrosheet or dialogsheet return } if ws.SheetViews == nil { ws.SheetViews = &xlsxSheetViews{ SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, } } if len(ws.SheetViews.SheetView) > 0 { ws.SheetViews.SheetView[0].TabSelected = false } if index == idx { if len(ws.SheetViews.SheetView) > 0 { ws.SheetViews.SheetView[0].TabSelected = true } else { ws.SheetViews.SheetView = append(ws.SheetViews.SheetView, xlsxSheetView{ TabSelected: true, }) } } } } // GetActiveSheetIndex provides a function to get active sheet index of the // spreadsheet. If not found the active sheet will be return integer 0. func (f *File) GetActiveSheetIndex() (index int) { sheetID := f.getActiveSheetID() wb, _ := f.workbookReader() if wb != nil { for idx, sheet := range wb.Sheets.Sheet { if sheet.SheetID == sheetID { index = idx return } } } return } // getActiveSheetID provides a function to get active sheet ID of the // spreadsheet. If not found the active sheet will be return integer 0. func (f *File) getActiveSheetID() int { wb, _ := f.workbookReader() if wb != nil { if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 { activeTab := wb.BookViews.WorkBookView[0].ActiveTab if len(wb.Sheets.Sheet) > activeTab && wb.Sheets.Sheet[activeTab].SheetID != 0 { return wb.Sheets.Sheet[activeTab].SheetID } } if len(wb.Sheets.Sheet) >= 1 { return wb.Sheets.Sheet[0].SheetID } } return 0 } // SetSheetName provides a function to set the worksheet name by given source and // target worksheet names. Maximum 31 characters are allowed in sheet title and // this function only changes the name of the sheet and will not update the // sheet name in the formula or reference associated with the cell. So there // may be problem formula error or reference missing. func (f *File) SetSheetName(source, target string) error { var err error if err = checkSheetName(source); err != nil { return err } if err = checkSheetName(target); err != nil { return err } if target == source { return err } f.clearCalcCache() wb, _ := f.workbookReader() for k, v := range wb.Sheets.Sheet { if v.Name == source { wb.Sheets.Sheet[k].Name = target f.sheetMap[target] = f.sheetMap[source] delete(f.sheetMap, source) } } if wb.DefinedNames == nil { return err } for i, dn := range wb.DefinedNames.DefinedName { wb.DefinedNames.DefinedName[i].Data = adjustRangeSheetName(dn.Data, source, target) } return err } // GetSheetName provides a function to get the sheet name of the workbook by // the given sheet index. If the given sheet index is invalid, it will return // an empty string. func (f *File) GetSheetName(index int) (name string) { for idx, sheet := range f.GetSheetList() { if idx == index { name = sheet return } } return } // getSheetID provides a function to get worksheet ID of the spreadsheet by // given sheet name. If given worksheet name is invalid, will return an // integer type value -1. func (f *File) getSheetID(sheet string) int { for sheetID, name := range f.GetSheetMap() { if strings.EqualFold(name, sheet) { return sheetID } } return -1 } // GetSheetIndex provides a function to get a sheet index of the workbook by // the given sheet name. If the given sheet name is invalid or sheet doesn't // exist, it will return an integer type value -1. func (f *File) GetSheetIndex(sheet string) (int, error) { if err := checkSheetName(sheet); err != nil { return -1, err } for index, name := range f.GetSheetList() { if strings.EqualFold(name, sheet) { return index, nil } } return -1, nil } // GetSheetMap provides a function to get worksheets, chart sheets, dialog // sheets ID and name map of the workbook. For example: // // f, err := excelize.OpenFile("Book1.xlsx") // if err != nil { // return // } // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // for index, name := range f.GetSheetMap() { // fmt.Println(index, name) // } func (f *File) GetSheetMap() map[int]string { wb, _ := f.workbookReader() sheetMap := map[int]string{} if wb != nil { for _, sheet := range wb.Sheets.Sheet { sheetMap[sheet.SheetID] = sheet.Name } } return sheetMap } // GetSheetList provides a function to get worksheets, chart sheets, and // dialog sheets name list of the workbook. func (f *File) GetSheetList() (list []string) { wb, _ := f.workbookReader() if wb != nil { for _, sheet := range wb.Sheets.Sheet { list = append(list, sheet.Name) } } return } // getSheetMap provides a function to get worksheet name and XML file path map // of the spreadsheet. func (f *File) getSheetMap() (map[string]string, error) { maps := map[string]string{} wb, err := f.workbookReader() if err != nil { return nil, err } rels, err := f.relsReader(f.getWorkbookRelsPath()) if err != nil { return nil, err } if rels == nil { return maps, nil } for _, v := range wb.Sheets.Sheet { for _, rel := range rels.Relationships { if rel.ID == v.ID { sheetXMLPath := f.getWorksheetPath(rel.Target) if _, ok := f.Pkg.Load(sheetXMLPath); ok { maps[v.Name] = sheetXMLPath } if _, ok := f.tempFiles.Load(sheetXMLPath); ok { maps[v.Name] = sheetXMLPath } } } } return maps, nil } // getSheetXMLPath provides a function to get XML file path by given sheet // name. func (f *File) getSheetXMLPath(sheet string) (string, bool) { var ( name string ok bool ) for sheetName, filePath := range f.sheetMap { if strings.EqualFold(sheetName, sheet) { name, ok = filePath, true break } } return name, ok } // SetSheetBackground provides a function to set background picture by given // worksheet name and file path. Supported image types: BMP, EMF, EMZ, GIF, ICO, // JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. func (f *File) SetSheetBackground(sheet, picture string) error { var err error // Check picture exists first. if _, err = os.Stat(picture); os.IsNotExist(err) { return err } file, _ := os.ReadFile(filepath.Clean(picture)) return f.setSheetBackground(sheet, path.Ext(picture), file) } // SetSheetBackgroundFromBytes provides a function to set background picture by // given worksheet name, extension name and image data. Supported image types: // BMP, EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. func (f *File) SetSheetBackgroundFromBytes(sheet, extension string, picture []byte) error { if len(picture) == 0 { return ErrParameterInvalid } return f.setSheetBackground(sheet, extension, picture) } // setSheetBackground provides a function to set background picture by given // worksheet name, file name extension and image data. func (f *File) setSheetBackground(sheet, extension string, file []byte) error { imageType, ok := supportedImageTypes[strings.ToLower(extension)] if !ok { return ErrImgExt } name := f.addMedia(file, imageType) sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") if err := f.addSheetPicture(sheet, rID); err != nil { return err } f.addSheetNameSpace(sheet, SourceRelationship) return f.setContentTypePartImageExtensions() } // DeleteSheet provides a function to delete worksheet in a workbook by given // worksheet name. Use this method with caution, which will affect changes in // references such as formulas, charts, and so on. If there is any referenced // value of the deleted worksheet, it will cause a file error when you open // it. This function will be invalid when only one worksheet is left. func (f *File) DeleteSheet(sheet string) error { if err := checkSheetName(sheet); err != nil { return err } if idx, _ := f.GetSheetIndex(sheet); f.SheetCount == 1 || idx == -1 { return nil } f.clearCalcCache() wb, _ := f.workbookReader() wbRels, _ := f.relsReader(f.getWorkbookRelsPath()) activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) deleteLocalSheetID, _ := f.GetSheetIndex(sheet) deleteAndAdjustDefinedNames(wb, deleteLocalSheetID) for idx, v := range wb.Sheets.Sheet { if !strings.EqualFold(v.Name, sheet) { continue } wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...) var sheetXML, rels string if wbRels != nil { for _, rel := range wbRels.Relationships { if rel.ID == v.ID { sheetXML = f.getWorksheetPath(rel.Target) sheetXMLPath, _ := f.getSheetXMLPath(sheet) rels = "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" } } } target := f.deleteSheetFromWorkbookRels(v.ID) _ = f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, target) _ = f.deleteCalcChain(f.getSheetID(sheet), "") delete(f.sheetMap, v.Name) f.Pkg.Delete(sheetXML) f.Pkg.Delete(rels) f.Relationships.Delete(rels) f.Sheet.Delete(sheetXML) f.xmlAttr.Delete(sheetXML) f.SheetCount-- } index, err := f.GetSheetIndex(activeSheetName) f.SetActiveSheet(index) return err } // MoveSheet moves a sheet to a specified position in the workbook. The function // moves the source sheet before the target sheet. After moving, other sheets // will be shifted to the left or right. If the sheet is already at the target // position, the function will not perform any action. Not that this function // will be ungroup all sheets after moving. For example, move Sheet2 before // Sheet1: // // err := f.MoveSheet("Sheet2", "Sheet1") func (f *File) MoveSheet(source, target string) error { if strings.EqualFold(source, target) { return nil } wb, err := f.workbookReader() if err != nil { return err } sourceIdx, err := f.GetSheetIndex(source) if err != nil { return err } targetIdx, err := f.GetSheetIndex(target) if err != nil { return err } if sourceIdx < 0 { return ErrSheetNotExist{source} } if targetIdx < 0 { return ErrSheetNotExist{target} } _ = f.UngroupSheets() activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) sourceSheet := wb.Sheets.Sheet[sourceIdx] wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...) if targetIdx > sourceIdx { targetIdx-- } wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...)...) activeSheetIdx, _ := f.GetSheetIndex(activeSheetName) f.SetActiveSheet(activeSheetIdx) return err } // deleteAndAdjustDefinedNames delete and adjust defined name in the workbook // by given worksheet ID. func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) { if wb == nil || wb.DefinedNames == nil { return } for idx := 0; idx < len(wb.DefinedNames.DefinedName); idx++ { dn := wb.DefinedNames.DefinedName[idx] if dn.LocalSheetID != nil { localSheetID := *dn.LocalSheetID if localSheetID == deleteLocalSheetID { wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) idx-- } else if localSheetID > deleteLocalSheetID { wb.DefinedNames.DefinedName[idx].LocalSheetID = intPtr(*dn.LocalSheetID - 1) } } } } // deleteSheetFromWorkbookRels provides a function to remove worksheet // relationships by given relationships ID in the file workbook.xml.rels. func (f *File) deleteSheetFromWorkbookRels(rID string) string { rels, _ := f.relsReader(f.getWorkbookRelsPath()) rels.mu.Lock() defer rels.mu.Unlock() for k, v := range rels.Relationships { if v.ID == rID { rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...) return v.Target } } return "" } // deleteSheetRelationships provides a function to delete relationships in // xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and // relationship index. func (f *File) deleteSheetRelationships(sheet, rID string) { name, ok := f.getSheetXMLPath(sheet) if !ok { name = strings.ToLower(sheet) + ".xml" } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" sheetRels, _ := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} } sheetRels.mu.Lock() defer sheetRels.mu.Unlock() for k, v := range sheetRels.Relationships { if v.ID == rID { sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...) } } f.Relationships.Store(rels, sheetRels) } // getSheetRelationshipsTargetByID provides a function to get Target attribute // value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and // relationship index. func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { name, ok := f.getSheetXMLPath(sheet) if !ok { name = strings.ToLower(sheet) + ".xml" } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" sheetRels, _ := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} } sheetRels.mu.Lock() defer sheetRels.mu.Unlock() for _, v := range sheetRels.Relationships { if v.ID == rID { return v.Target } } return "" } // CopySheet provides a function to duplicate a worksheet by gave source and // target worksheet index. Note that currently doesn't support duplicate // workbooks that contain tables, charts or pictures. For Example: // // // Sheet1 already exists... // index, err := f.NewSheet("Sheet2") // if err != nil { // fmt.Println(err) // return // } // err = f.CopySheet(0, index) func (f *File) CopySheet(from, to int) error { if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" { return ErrSheetIdx } return f.copySheet(from, to) } // copySheet provides a function to duplicate a worksheet by gave source and // target worksheet name. func (f *File) copySheet(from, to int) error { fromSheet := f.GetSheetName(from) sheet, err := f.workSheetReader(fromSheet) if err != nil { return err } f.clearCalcCache() worksheet := &xlsxWorksheet{} err = deepcopy.Copy(worksheet, sheet) toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to))) sheetXMLPath := "xl/worksheets/sheet" + toSheetID + ".xml" if len(worksheet.SheetViews.SheetView) > 0 { worksheet.SheetViews.SheetView[0].TabSelected = false } worksheet.Drawing = nil worksheet.TableParts = nil worksheet.PageSetUp = nil f.Sheet.Store(sheetXMLPath, worksheet) toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels" fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels" if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil { f.Pkg.Store(toRels, rels.([]byte)) } fromSheetXMLPath, _ := f.getSheetXMLPath(fromSheet) fromSheetAttr, _ := f.xmlAttr.Load(fromSheetXMLPath) f.xmlAttr.Store(sheetXMLPath, fromSheetAttr) return err } // getSheetState returns sheet visible enumeration by given hidden status. func getSheetState(visible bool, veryHidden []bool) string { state := "hidden" if !visible && len(veryHidden) > 0 && veryHidden[0] { state = "veryHidden" } return state } // SetSheetVisible provides a function to set worksheet visible by given // worksheet name. A workbook must contain at least one visible worksheet. If // the given worksheet has been activated, this setting will be invalidated. // The third optional veryHidden parameter only works when visible was false. // // For example, hide Sheet1: // // err := f.SetSheetVisible("Sheet1", false) func (f *File) SetSheetVisible(sheet string, visible bool, veryHidden ...bool) error { if err := checkSheetName(sheet); err != nil { return err } wb, err := f.workbookReader() if err != nil { return err } if visible { for k, v := range wb.Sheets.Sheet { if strings.EqualFold(v.Name, sheet) { wb.Sheets.Sheet[k].State = "" } } return err } count, state := 0, getSheetState(visible, veryHidden) for _, v := range wb.Sheets.Sheet { if v.State != state { count++ } } for k, v := range wb.Sheets.Sheet { ws, err := f.workSheetReader(v.Name) if err != nil { return err } tabSelected := false if ws.SheetViews == nil { ws.SheetViews = &xlsxSheetViews{ SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, } } if len(ws.SheetViews.SheetView) > 0 { tabSelected = ws.SheetViews.SheetView[0].TabSelected } if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected { wb.Sheets.Sheet[k].State = state } } return err } // setPanes set create freeze panes and split panes by given options. func (ws *xlsxWorksheet) setPanes(panes *Panes) error { if panes == nil { return ErrParameterInvalid } p := &xlsxPane{ ActivePane: panes.ActivePane, TopLeftCell: panes.TopLeftCell, XSplit: float64(panes.XSplit), YSplit: float64(panes.YSplit), } if panes.Freeze { p.State = "frozen" } if ws.SheetViews == nil { ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}} } ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p if !(panes.Freeze) && !(panes.Split) { if len(ws.SheetViews.SheetView) > 0 { ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil } } var s []*xlsxSelection for _, p := range panes.Selection { s = append(s, &xlsxSelection{ ActiveCell: p.ActiveCell, Pane: p.Pane, SQRef: p.SQRef, }) } ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s return nil } // SetPanes provides a function to create and remove freeze panes and split panes // by given worksheet name and panes options. // // ActivePane defines the pane that is active. The possible values for this // attribute are defined in the following table: // // Enumeration Value | Description // ---------------------------------+------------------------------------------------------------- // bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal // | splits are applied. // | // | This value is also used when only a horizontal split has // | been applied, dividing the pane into upper and lower // | regions. In that case, this value specifies the bottom // | pane. // | // bottomRight (Bottom Right Pane) | Bottom right pane, when both vertical and horizontal // | splits are applied. // | // topLeft (Top Left Pane) | Top left pane, when both vertical and horizontal splits // | are applied. // | // | This value is also used when only a horizontal split has // | been applied, dividing the pane into upper and lower // | regions. In that case, this value specifies the top pane. // | // | This value is also used when only a vertical split has // | been applied, dividing the pane into right and left // | regions. In that case, this value specifies the left pane // | // topRight (Top Right Pane) | Top right pane, when both vertical and horizontal // | splits are applied. // | // | This value is also used when only a vertical split has // | been applied, dividing the pane into right and left // | regions. In that case, this value specifies the right // | pane. // // Pane state type is restricted to the values supported currently listed in the following table: // // Enumeration Value | Description // ---------------------------------+------------------------------------------------------------- // frozen (Frozen) | Panes are frozen, but were not split being frozen. In // | this state, when the panes are unfrozen again, a single // | pane results, with no split. // | // | In this state, the split bars are not adjustable. // | // split (Split) | Panes are split, but not frozen. In this state, the split // | bars are adjustable by the user. // // XSplit (Horizontal Split Position): Horizontal position of the split, in // 1/20th of a point; 0 (zero) if none. If the pane is frozen, this value // indicates the number of columns visible in the top pane. // // YSplit (Vertical Split Position): Vertical position of the split, in 1/20th // of a point; 0 (zero) if none. If the pane is frozen, this value indicates the // number of rows visible in the left pane. The possible values for this // attribute are defined by the W3C XML Schema double datatype. // // TopLeftCell: Location of the top left visible cell in the bottom right pane // (when in Left-To-Right mode). // // SQRef (Sequence of References): Range of the selection. Can be non-contiguous // set of ranges. // // An example of how to freeze column A in the Sheet1 and set the active cell on // Sheet1!K16: // // err := f.SetPanes("Sheet1", &excelize.Panes{ // Freeze: true, // Split: false, // XSplit: 1, // YSplit: 0, // TopLeftCell: "B1", // ActivePane: "topRight", // Selection: []excelize.Selection{ // {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, // }, // }) // // An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell // ranges on Sheet1!A11:XFD11: // // err := f.SetPanes("Sheet1", &excelize.Panes{ // Freeze: true, // Split: false, // XSplit: 0, // YSplit: 9, // TopLeftCell: "A34", // ActivePane: "bottomLeft", // Selection: []excelize.Selection{ // {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"}, // }, // }) // // An example of how to create split panes in the Sheet1 and set the active cell // on Sheet1!J60: // // err := f.SetPanes("Sheet1", &excelize.Panes{ // Freeze: false, // Split: true, // XSplit: 3270, // YSplit: 1800, // TopLeftCell: "N57", // ActivePane: "bottomLeft", // Selection: []excelize.Selection{ // {SQRef: "I36", ActiveCell: "I36"}, // {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"}, // {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"}, // {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"}, // }, // }) // // An example of how to unfreeze and remove all panes on Sheet1: // // err := f.SetPanes("Sheet1", &excelize.Panes{Freeze: false, Split: false}) func (f *File) SetPanes(sheet string, panes *Panes) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } return ws.setPanes(panes) } // getPanes returns freeze panes, split panes, and views of the worksheet. func (ws *xlsxWorksheet) getPanes() Panes { var ( panes Panes section []Selection ) if ws.SheetViews == nil || len(ws.SheetViews.SheetView) < 1 { return panes } sw := ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1] for _, s := range sw.Selection { if s != nil { section = append(section, Selection{ SQRef: s.SQRef, ActiveCell: s.ActiveCell, Pane: s.Pane, }) } } panes.Selection = section if sw.Pane == nil { return panes } panes.ActivePane = sw.Pane.ActivePane if sw.Pane.State == "frozen" { panes.Freeze = true } panes.TopLeftCell = sw.Pane.TopLeftCell panes.XSplit = int(sw.Pane.XSplit) panes.YSplit = int(sw.Pane.YSplit) return panes } // GetPanes provides a function to get freeze panes, split panes, and worksheet // views by given worksheet name. func (f *File) GetPanes(sheet string) (Panes, error) { var panes Panes ws, err := f.workSheetReader(sheet) if err != nil { return panes, err } return ws.getPanes(), err } // GetSheetVisible provides a function to get worksheet visible by given worksheet // name. For example, get visible state of Sheet1: // // visible, err := f.GetSheetVisible("Sheet1") func (f *File) GetSheetVisible(sheet string) (bool, error) { var visible bool if err := checkSheetName(sheet); err != nil { return visible, err } wb, _ := f.workbookReader() for k, v := range wb.Sheets.Sheet { if strings.EqualFold(v.Name, sheet) { if wb.Sheets.Sheet[k].State == "" || wb.Sheets.Sheet[k].State == "visible" { visible = true } } } return visible, nil } // SearchSheet provides a function to get cell reference by given worksheet name, // cell value, and regular expression. The function doesn't support searching // on the calculated result, formatted numbers and conditional lookup // currently. If it is a merged cell, it will return the cell reference of the // upper left cell of the merged range reference. // // An example of search the cell reference of the value of "100" on Sheet1: // // result, err := f.SearchSheet("Sheet1", "100") // // An example of search the cell reference where the numerical value in the range // of "0-9" of Sheet1 is described: // // result, err := f.SearchSheet("Sheet1", "[0-9]", true) func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { var ( regSearch bool result []string ) if err := checkSheetName(sheet); err != nil { return result, err } for _, r := range reg { regSearch = r } name, ok := f.getSheetXMLPath(sheet) if !ok { return result, ErrSheetNotExist{sheet} } if ws, ok := f.Sheet.Load(name); ok && ws != nil { // Flush data output, _ := xml.Marshal(ws.(*xlsxWorksheet)) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } return f.searchSheet(name, value, regSearch) } // searchSheet provides a function to get cell reference by given worksheet // name, cell value, and regular expression. func (f *File) searchSheet(name, value string, regSearch bool) (result []string, err error) { var ( cellName, inElement string cellCol, row int sst *xlsxSST ) if sst, err = f.sharedStringsReader(); err != nil { return } regex := regexp.MustCompile(value) decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name))) for { var token xml.Token token, err = decoder.Token() if err != nil || token == nil { if err == io.EOF { err = nil } break } switch xmlElement := token.(type) { case xml.StartElement: inElement = xmlElement.Name.Local if inElement == "row" { row, err = attrValToInt("r", xmlElement.Attr) if err != nil { return } } if inElement == "c" { colCell := xlsxC{} _ = decoder.DecodeElement(&colCell, &xmlElement) val, _ := colCell.getValueFrom(f, sst, false) if regSearch { if !regex.MatchString(val) { continue } } else { if val != value { continue } } cellCol, _, err = CellNameToCoordinates(colCell.R) if err != nil { return result, err } cellName, err = CoordinatesToCellName(cellCol, row) if err != nil { return result, err } result = append(result, cellName) } default: } } return } // attrValToInt provides a function to convert the local names to an integer // by given XML attributes and specified names. func attrValToInt(name string, attrs []xml.Attr) (val int, err error) { for _, attr := range attrs { if attr.Name.Local == name { val, err = strconv.Atoi(attr.Value) if err != nil { return } } } return } // attrValToFloat provides a function to convert the local names to a float64 // by given XML attributes and specified names. func attrValToFloat(name string, attrs []xml.Attr) (val float64, err error) { for _, attr := range attrs { if attr.Name.Local == name { val, err = strconv.ParseFloat(attr.Value, 64) if err != nil { return } } } return } // attrValToBool provides a function to convert the local names to a boolean // by given XML attributes and specified names. func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) { for _, attr := range attrs { if attr.Name.Local == name { val, err = strconv.ParseBool(attr.Value) if err != nil { return } } } return } // SetHeaderFooter provides a function to set headers and footers by given // worksheet name and the control characters. // // Headers and footers are specified using the following settings fields: // // Fields | Description // ------------------+----------------------------------------------------------- // AlignWithMargins | Align header footer margins with page margins // DifferentFirst | Different first-page header and footer indicator // DifferentOddEven | Different odd and even page headers and footers indicator // ScaleWithDoc | Scale header and footer with document scaling // OddFooter | Odd Page Footer, or primary Page Footer if 'DifferentOddEven' is 'false' // OddHeader | Odd Header, or primary Page Header if 'DifferentOddEven' is 'false' // EvenFooter | Even Page Footer // EvenHeader | Even Page Header // FirstFooter | First Page Footer // FirstHeader | First Page Header // // The following formatting codes can be used in 6 string type fields: // OddHeader, OddFooter, EvenHeader, EvenFooter, FirstFooter, FirstHeader // // Formatting Code | Description // ------------------------+------------------------------------------------------------------------- // && | The character "&" // | // &font-size | Size of the text font, where font-size is a decimal font size in points // | // &"font name,font type" | A text font-name string, font name, and a text font-type string, // | font type // | // &"-,Regular" | Regular text format. Toggles bold and italic modes to off // | // &A | Current worksheet's tab name // | // &B or &"-,Bold" | Bold text format, from off to on, or vice versa. The default mode is off // | // &D | Current date // | // &C | Center section // | // &E | Double-underline text format // | // &F | Current workbook's file name // | // &G | Drawing object as background (Use AddHeaderFooterImage) // | // &H | Shadow text format // | // &I or &"-,Italic" | Italic text format // | // &K | Text font color // | // | An RGB Color is specified as RRGGBB // | // | A Theme Color is specified as TTSNNN where TT is the theme color Id, // | S is either "+" or "-" of the tint/shade value, and NNN is the // | tint/shade value // | // &L | Left section // | // &N | Total number of pages // | // &O | Outline text format // | // &P[[+|-]n] | Without the optional suffix, the current page number in decimal // | // &R | Right section // | // &S | Strike through text format // | // &T | Current time // | // &U | Single-underline text format. If double-underline mode is on, the next // | occurrence in a section specifier toggles double-underline mode to off; // | otherwise, it toggles single-underline mode, from off to on, or vice // | versa. The default mode is off // | // &X | Superscript text format // | // &Y | Subscript text format // | // &Z | Current workbook's file path // // For example: // // err := f.SetHeaderFooter("Sheet1", &excelize.HeaderFooterOptions{ // DifferentFirst: true, // DifferentOddEven: true, // OddHeader: "&R&P", // OddFooter: "&C&F", // EvenHeader: "&L&P", // EvenFooter: "&L&D&R&T", // FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`, // }) // // This example shows: // // - The first page has its own header and footer // // - Odd and even-numbered pages have different headers and footers // // - Current page number in the right section of odd-page headers // // - Current workbook's file name in the center section of odd-page footers // // - Current page number in the left section of even-page headers // // - Current date in the left section and the current time in the right section // of even-page footers // // - The text "Center Bold Header" on the first line of the center section of // the first page, and the date on the second line of the center section of // that same page // // - No footer on the first page func (f *File) SetHeaderFooter(sheet string, opts *HeaderFooterOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if opts == nil { ws.HeaderFooter = nil return err } v := reflect.ValueOf(*opts) // Check 6 string type fields: OddHeader, OddFooter, EvenHeader, EvenFooter, // FirstFooter, FirstHeader for i := 4; i < v.NumField()-1; i++ { if countUTF16String(v.Field(i).String()) > MaxFieldLength { return newFieldLengthError(v.Type().Field(i).Name) } } ws.HeaderFooter = &xlsxHeaderFooter{ AlignWithMargins: opts.AlignWithMargins, DifferentFirst: opts.DifferentFirst, DifferentOddEven: opts.DifferentOddEven, ScaleWithDoc: opts.ScaleWithDoc, OddHeader: opts.OddHeader, OddFooter: opts.OddFooter, EvenHeader: opts.EvenHeader, EvenFooter: opts.EvenFooter, FirstFooter: opts.FirstFooter, FirstHeader: opts.FirstHeader, } return err } // GetHeaderFooter provides a function to get worksheet header and footer by // given worksheet name. func (f *File) GetHeaderFooter(sheet string) (*HeaderFooterOptions, error) { var opts *HeaderFooterOptions ws, err := f.workSheetReader(sheet) if err != nil { return opts, err } if ws.HeaderFooter == nil { return opts, err } opts = &HeaderFooterOptions{ AlignWithMargins: ws.HeaderFooter.AlignWithMargins, DifferentFirst: ws.HeaderFooter.DifferentFirst, DifferentOddEven: ws.HeaderFooter.DifferentOddEven, ScaleWithDoc: ws.HeaderFooter.ScaleWithDoc, OddHeader: ws.HeaderFooter.OddHeader, OddFooter: ws.HeaderFooter.OddFooter, EvenHeader: ws.HeaderFooter.EvenHeader, EvenFooter: ws.HeaderFooter.EvenFooter, FirstHeader: ws.HeaderFooter.FirstHeader, FirstFooter: ws.HeaderFooter.FirstFooter, } return opts, err } // ProtectSheet provides a function to prevent other users from accidentally or // deliberately changing, moving, or deleting data in a worksheet. The // optional field AlgorithmName specified hash algorithm, support XOR, MD4, // MD5, SHA-1, SHA2-56, SHA-384, and SHA-512 currently, if no hash algorithm // specified, will be using the XOR algorithm as default. For example, protect // Sheet1 with protection settings: // // err := f.ProtectSheet("Sheet1", &excelize.SheetProtectionOptions{ // AlgorithmName: "SHA-512", // Password: "password", // SelectLockedCells: true, // SelectUnlockedCells: true, // EditScenarios: true, // }) func (f *File) ProtectSheet(sheet string, opts *SheetProtectionOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if opts == nil { return ErrParameterInvalid } ws.SheetProtection = &xlsxSheetProtection{ AutoFilter: !opts.AutoFilter, DeleteColumns: !opts.DeleteColumns, DeleteRows: !opts.DeleteRows, FormatCells: !opts.FormatCells, FormatColumns: !opts.FormatColumns, FormatRows: !opts.FormatRows, InsertColumns: !opts.InsertColumns, InsertHyperlinks: !opts.InsertHyperlinks, InsertRows: !opts.InsertRows, Objects: !opts.EditObjects, PivotTables: !opts.PivotTables, Scenarios: !opts.EditScenarios, SelectLockedCells: !opts.SelectLockedCells, SelectUnlockedCells: !opts.SelectUnlockedCells, Sheet: true, Sort: !opts.Sort, } if opts.Password != "" { if opts.AlgorithmName == "" { ws.SheetProtection.Password = genSheetPasswd(opts.Password) return err } hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(sheetProtectionSpinCount)) if err != nil { return err } ws.SheetProtection.Password = "" ws.SheetProtection.AlgorithmName = opts.AlgorithmName ws.SheetProtection.SaltValue = saltValue ws.SheetProtection.HashValue = hashValue ws.SheetProtection.SpinCount = int(sheetProtectionSpinCount) } return err } // UnprotectSheet provides a function to remove protection for a sheet, // specified the second optional password parameter to remove sheet // protection with password verification. func (f *File) UnprotectSheet(sheet string, password ...string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } // password verification if len(password) > 0 { if ws.SheetProtection == nil { return ErrUnprotectSheet } if ws.SheetProtection.AlgorithmName == "" && ws.SheetProtection.Password != genSheetPasswd(password[0]) { return ErrUnprotectSheetPassword } if ws.SheetProtection.AlgorithmName != "" { // check with given salt value hashValue, _, err := genISOPasswdHash(password[0], ws.SheetProtection.AlgorithmName, ws.SheetProtection.SaltValue, ws.SheetProtection.SpinCount) if err != nil { return err } if ws.SheetProtection.HashValue != hashValue { return ErrUnprotectSheetPassword } } } ws.SheetProtection = nil return err } // GetSheetProtection provides a function to get worksheet protection settings // by given worksheet name. Note that the password in the returned will always // be empty. func (f *File) GetSheetProtection(sheet string) (SheetProtectionOptions, error) { var opts SheetProtectionOptions ws, err := f.workSheetReader(sheet) if err != nil { return opts, err } if ws.SheetProtection != nil { opts.AlgorithmName = ws.SheetProtection.AlgorithmName opts.AutoFilter = !ws.SheetProtection.AutoFilter opts.DeleteColumns = !ws.SheetProtection.DeleteColumns opts.DeleteRows = !ws.SheetProtection.DeleteRows opts.EditObjects = !ws.SheetProtection.Objects opts.EditScenarios = !ws.SheetProtection.Scenarios opts.FormatCells = !ws.SheetProtection.FormatCells opts.FormatColumns = !ws.SheetProtection.FormatColumns opts.FormatRows = !ws.SheetProtection.FormatRows opts.InsertColumns = !ws.SheetProtection.InsertColumns opts.InsertHyperlinks = !ws.SheetProtection.InsertHyperlinks opts.InsertRows = !ws.SheetProtection.InsertRows opts.PivotTables = !ws.SheetProtection.PivotTables opts.SelectLockedCells = !ws.SheetProtection.SelectLockedCells opts.SelectUnlockedCells = !ws.SheetProtection.SelectUnlockedCells opts.Sort = !ws.SheetProtection.Sort } return opts, err } // checkSheetName check whether there are illegal characters in the sheet name. // 1. Confirm that the sheet name is not empty // 2. Make sure to enter a name with no more than 31 characters // 3. Make sure the first or last character of the name cannot be a single quote // 4. Verify that the following characters are not included in the name :\/?*[] func checkSheetName(name string) error { if name == "" { return ErrSheetNameBlank } if countUTF16String(name) > MaxSheetNameLength { return ErrSheetNameLength } if strings.HasPrefix(name, "'") || strings.HasSuffix(name, "'") { return ErrSheetNameSingleQuote } if strings.ContainsAny(name, ":\\/?*[]") { return ErrSheetNameInvalid } return nil } // SetPageLayout provides a function to sets worksheet page layout. // // The following shows the paper size sorted by excelize index number: // // Index | Paper Size // -------+----------------------------------------------- // 1 | Letter paper (8.5 in. by 11 in.) // 2 | Letter small paper (8.5 in. by 11 in.) // 3 | Tabloid paper (11 in. by 17 in.) // 4 | Ledger paper (17 in. by 11 in.) // 5 | Legal paper (8.5 in. by 14 in.) // 6 | Statement paper (5.5 in. by 8.5 in.) // 7 | Executive paper (7.25 in. by 10.5 in.) // 8 | A3 paper (297 mm by 420 mm) // 9 | A4 paper (210 mm by 297 mm) // 10 | A4 small paper (210 mm by 297 mm) // 11 | A5 paper (148 mm by 210 mm) // 12 | B4 paper (250 mm by 353 mm) // 13 | B5 paper (176 mm by 250 mm) // 14 | Folio paper (8.5 in. by 13 in.) // 15 | Quarto paper (215 mm by 275 mm) // 16 | Standard paper (10 in. by 14 in.) // 17 | Standard paper (11 in. by 17 in.) // 18 | Note paper (8.5 in. by 11 in.) // 19 | #9 envelope (3.875 in. by 8.875 in.) // 20 | #10 envelope (4.125 in. by 9.5 in.) // 21 | #11 envelope (4.5 in. by 10.375 in.) // 22 | #12 envelope (4.75 in. by 11 in.) // 23 | #14 envelope (5 in. by 11.5 in.) // 24 | C paper (17 in. by 22 in.) // 25 | D paper (22 in. by 34 in.) // 26 | E paper (34 in. by 44 in.) // 27 | DL envelope (110 mm by 220 mm) // 28 | C5 envelope (162 mm by 229 mm) // 29 | C3 envelope (324 mm by 458 mm) // 30 | C4 envelope (229 mm by 324 mm) // 31 | C6 envelope (114 mm by 162 mm) // 32 | C65 envelope (114 mm by 229 mm) // 33 | B4 envelope (250 mm by 353 mm) // 34 | B5 envelope (176 mm by 250 mm) // 35 | B6 envelope (176 mm by 125 mm) // 36 | Italy envelope (110 mm by 230 mm) // 37 | Monarch envelope (3.875 in. by 7.5 in.). // 38 | 6 3/4 envelope (3.625 in. by 6.5 in.) // 39 | US standard fanfold (14.875 in. by 11 in.) // 40 | German standard fanfold (8.5 in. by 12 in.) // 41 | German legal fanfold (8.5 in. by 13 in.) // 42 | ISO B4 (250 mm by 353 mm) // 43 | Japanese postcard (100 mm by 148 mm) // 44 | Standard paper (9 in. by 11 in.) // 45 | Standard paper (10 in. by 11 in.) // 46 | Standard paper (15 in. by 11 in.) // 47 | Invite envelope (220 mm by 220 mm) // 50 | Letter extra paper (9.275 in. by 12 in.) // 51 | Legal extra paper (9.275 in. by 15 in.) // 52 | Tabloid extra paper (11.69 in. by 18 in.) // 53 | A4 extra paper (236 mm by 322 mm) // 54 | Letter transverse paper (8.275 in. by 11 in.) // 55 | A4 transverse paper (210 mm by 297 mm) // 56 | Letter extra transverse paper (9.275 in. by 12 in.) // 57 | SuperA/SuperA/A4 paper (227 mm by 356 mm) // 58 | SuperB/SuperB/A3 paper (305 mm by 487 mm) // 59 | Letter plus paper (8.5 in. by 12.69 in.) // 60 | A4 plus paper (210 mm by 330 mm) // 61 | A5 transverse paper (148 mm by 210 mm) // 62 | JIS B5 transverse paper (182 mm by 257 mm) // 63 | A3 extra paper (322 mm by 445 mm) // 64 | A5 extra paper (174 mm by 235 mm) // 65 | ISO B5 extra paper (201 mm by 276 mm) // 66 | A2 paper (420 mm by 594 mm) // 67 | A3 transverse paper (297 mm by 420 mm) // 68 | A3 extra transverse paper (322 mm by 445 mm) // 69 | Japanese Double Postcard (200 mm x 148 mm) // 70 | A6 (105 mm x 148 mm) // 71 | Japanese Envelope Kaku #2 // 72 | Japanese Envelope Kaku #3 // 73 | Japanese Envelope Chou #3 // 74 | Japanese Envelope Chou #4 // 75 | Letter Rotated (11in x 8 1/2 11 in) // 76 | A3 Rotated (420 mm x 297 mm) // 77 | A4 Rotated (297 mm x 210 mm) // 78 | A5 Rotated (210 mm x 148 mm) // 79 | B4 (JIS) Rotated (364 mm x 257 mm) // 80 | B5 (JIS) Rotated (257 mm x 182 mm) // 81 | Japanese Postcard Rotated (148 mm x 100 mm) // 82 | Double Japanese Postcard Rotated (148 mm x 200 mm) // 83 | A6 Rotated (148 mm x 105 mm) // 84 | Japanese Envelope Kaku #2 Rotated // 85 | Japanese Envelope Kaku #3 Rotated // 86 | Japanese Envelope Chou #3 Rotated // 87 | Japanese Envelope Chou #4 Rotated // 88 | B6 (JIS) (128 mm x 182 mm) // 89 | B6 (JIS) Rotated (182 mm x 128 mm) // 90 | (12 in x 11 in) // 91 | Japanese Envelope You #4 // 92 | Japanese Envelope You #4 Rotated // 93 | PRC 16K (146 mm x 215 mm) // 94 | PRC 32K (97 mm x 151 mm) // 95 | PRC 32K(Big) (97 mm x 151 mm) // 96 | PRC Envelope #1 (102 mm x 165 mm) // 97 | PRC Envelope #2 (102 mm x 176 mm) // 98 | PRC Envelope #3 (125 mm x 176 mm) // 99 | PRC Envelope #4 (110 mm x 208 mm) // 100 | PRC Envelope #5 (110 mm x 220 mm) // 101 | PRC Envelope #6 (120 mm x 230 mm) // 102 | PRC Envelope #7 (160 mm x 230 mm) // 103 | PRC Envelope #8 (120 mm x 309 mm) // 104 | PRC Envelope #9 (229 mm x 324 mm) // 105 | PRC Envelope #10 (324 mm x 458 mm) // 106 | PRC 16K Rotated // 107 | PRC 32K Rotated // 108 | PRC 32K(Big) Rotated // 109 | PRC Envelope #1 Rotated (165 mm x 102 mm) // 110 | PRC Envelope #2 Rotated (176 mm x 102 mm) // 111 | PRC Envelope #3 Rotated (176 mm x 125 mm) // 112 | PRC Envelope #4 Rotated (208 mm x 110 mm) // 113 | PRC Envelope #5 Rotated (220 mm x 110 mm) // 114 | PRC Envelope #6 Rotated (230 mm x 120 mm) // 115 | PRC Envelope #7 Rotated (230 mm x 160 mm) // 116 | PRC Envelope #8 Rotated (309 mm x 120 mm) // 117 | PRC Envelope #9 Rotated (324 mm x 229 mm) // 118 | PRC Envelope #10 Rotated (458 mm x 324 mm) func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if opts == nil { return err } return ws.setPageSetUp(opts) } // newPageSetUp initialize page setup settings for the worksheet if which not // exist. func (ws *xlsxWorksheet) newPageSetUp() { if ws.PageSetUp == nil { ws.PageSetUp = new(xlsxPageSetUp) } } // setPageSetUp set page setup settings for the worksheet by given options. func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) error { if opts.Size != nil { ws.newPageSetUp() ws.PageSetUp.PaperSize = opts.Size } if opts.Orientation != nil { if inStrSlice(supportedPageOrientation, *opts.Orientation, true) == -1 { return newInvalidOptionalValue("Orientation", *opts.Orientation, supportedPageOrientation) } ws.newPageSetUp() ws.PageSetUp.Orientation = *opts.Orientation } if opts.FirstPageNumber != nil && *opts.FirstPageNumber > 0 { ws.newPageSetUp() ws.PageSetUp.FirstPageNumber = strconv.Itoa(int(*opts.FirstPageNumber)) ws.PageSetUp.UseFirstPageNumber = true } if opts.AdjustTo != nil { if *opts.AdjustTo < 10 || 400 < *opts.AdjustTo { return ErrPageSetupAdjustTo } ws.newPageSetUp() ws.PageSetUp.Scale = int(*opts.AdjustTo) } if opts.FitToHeight != nil { ws.newPageSetUp() ws.PageSetUp.FitToHeight = opts.FitToHeight } if opts.FitToWidth != nil { ws.newPageSetUp() ws.PageSetUp.FitToWidth = opts.FitToWidth } if opts.BlackAndWhite != nil { ws.newPageSetUp() ws.PageSetUp.BlackAndWhite = *opts.BlackAndWhite } if opts.PageOrder != nil { if inStrSlice(supportedPageOrder, *opts.PageOrder, true) == -1 { return newInvalidOptionalValue("PageOrder", *opts.PageOrder, supportedPageOrder) } ws.newPageSetUp() ws.PageSetUp.PageOrder = *opts.PageOrder } return nil } // GetPageLayout provides a function to gets worksheet page layout. func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) { opts := PageLayoutOptions{ Size: intPtr(0), Orientation: stringPtr(supportedPageOrientation[0]), FirstPageNumber: uintPtr(1), AdjustTo: uintPtr(100), } ws, err := f.workSheetReader(sheet) if err != nil { return opts, err } if ws.PageSetUp != nil { if ws.PageSetUp.PaperSize != nil { opts.Size = ws.PageSetUp.PaperSize } if ws.PageSetUp.Orientation != "" { opts.Orientation = stringPtr(ws.PageSetUp.Orientation) } if num, _ := strconv.Atoi(ws.PageSetUp.FirstPageNumber); num != 0 { opts.FirstPageNumber = uintPtr(uint(num)) } if ws.PageSetUp.Scale >= 10 && ws.PageSetUp.Scale <= 400 { opts.AdjustTo = uintPtr(uint(ws.PageSetUp.Scale)) } if ws.PageSetUp.FitToHeight != nil { opts.FitToHeight = ws.PageSetUp.FitToHeight } if ws.PageSetUp.FitToWidth != nil { opts.FitToWidth = ws.PageSetUp.FitToWidth } opts.BlackAndWhite = boolPtr(ws.PageSetUp.BlackAndWhite) if ws.PageSetUp.PageOrder != "" { opts.PageOrder = stringPtr(ws.PageSetUp.PageOrder) } } return opts, err } // SetDefinedName provides a function to set the defined names of the workbook // or worksheet. If not specified scope, the default scope is workbook. // For example: // // err := f.SetDefinedName(&excelize.DefinedName{ // Name: "Amount", // RefersTo: "Sheet1!$A$2:$D$5", // Comment: "defined name comment", // }) // // If you fill the RefersTo property with only one columns range without a // comma, it will work as "Columns to repeat at left" only. For example: // // err := f.SetDefinedName(&excelize.DefinedName{ // Name: "_xlnm.Print_Titles", // RefersTo: "Sheet1!$A:$A", // Scope: "Sheet1", // }) // // If you fill the RefersTo property with only one rows range without a comma, // it will work as "Rows to repeat at top" only. For example: // // err := f.SetDefinedName(&excelize.DefinedName{ // Name: "_xlnm.Print_Titles", // RefersTo: "Sheet1!$1:$1", // Scope: "Sheet1", // }) // // You can also use the function in RefersTo. For example: // // err := f.SetDefinedName(&excelize.DefinedName{ // Name: "CustomRange", // RefersTo: "Sheet1!$A$2+Sheet1!$D$5", // Scope: "Sheet1", // }) func (f *File) SetDefinedName(definedName *DefinedName) error { if definedName.Name == "" || definedName.RefersTo == "" { return ErrParameterInvalid } if err := checkDefinedName(definedName.Name); err != nil && inStrSlice(builtInDefinedNames[:2], definedName.Name, false) == -1 { return err } wb, err := f.workbookReader() if err != nil { return err } f.clearCalcCache() d := xlsxDefinedName{ Name: definedName.Name, Comment: definedName.Comment, Data: definedName.RefersTo, } if definedName.Scope != "" { if sheetIndex, _ := f.GetSheetIndex(definedName.Scope); sheetIndex >= 0 { d.LocalSheetID = &sheetIndex } } if wb.DefinedNames != nil { for _, dn := range wb.DefinedNames.DefinedName { var scope string if dn.LocalSheetID != nil { scope = f.GetSheetName(*dn.LocalSheetID) } if scope == definedName.Scope && dn.Name == definedName.Name { return ErrDefinedNameDuplicate } } wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) return nil } wb.DefinedNames = &xlsxDefinedNames{ DefinedName: []xlsxDefinedName{d}, } return nil } // DeleteDefinedName provides a function to delete the defined names of the // workbook or worksheet. If not specified scope, the default scope is // workbook. For example: // // err := f.DeleteDefinedName(&excelize.DefinedName{ // Name: "Amount", // Scope: "Sheet2", // }) func (f *File) DeleteDefinedName(definedName *DefinedName) error { wb, err := f.workbookReader() if err != nil { return err } f.clearCalcCache() if wb.DefinedNames != nil { for idx, dn := range wb.DefinedNames.DefinedName { scope := "Workbook" deleteScope := definedName.Scope if deleteScope == "" { deleteScope = "Workbook" } if dn.LocalSheetID != nil { scope = f.GetSheetName(*dn.LocalSheetID) } if scope == deleteScope && dn.Name == definedName.Name { wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) return err } } } return ErrDefinedNameScope } // GetDefinedName provides a function to get the defined names of the workbook // or worksheet. func (f *File) GetDefinedName() []DefinedName { var definedNames []DefinedName wb, _ := f.workbookReader() if wb.DefinedNames != nil { for _, dn := range wb.DefinedNames.DefinedName { definedName := DefinedName{ Name: dn.Name, Comment: dn.Comment, RefersTo: dn.Data, Scope: "Workbook", } if dn.LocalSheetID != nil && *dn.LocalSheetID >= 0 { definedName.Scope = f.GetSheetName(*dn.LocalSheetID) } definedNames = append(definedNames, definedName) } } return definedNames } // GroupSheets provides a function to group worksheets by given worksheets // name. Group worksheets must contain an active worksheet. func (f *File) GroupSheets(sheets []string) error { // Check an active worksheet in group worksheets var inActiveSheet bool activeSheet := f.GetActiveSheetIndex() sheetMap := f.GetSheetList() for idx, sheetName := range sheetMap { for _, s := range sheets { if strings.EqualFold(s, sheetName) && idx == activeSheet { inActiveSheet = true } } } if !inActiveSheet { return ErrGroupSheets } // check worksheet exists var wss []*xlsxWorksheet for _, sheet := range sheets { worksheet, err := f.workSheetReader(sheet) if err != nil { return err } wss = append(wss, worksheet) } for _, ws := range wss { sheetViews := ws.SheetViews.SheetView for idx := range sheetViews { ws.SheetViews.SheetView[idx].TabSelected = true } } return nil } // UngroupSheets provides a function to ungroup worksheets. func (f *File) UngroupSheets() error { activeSheet := f.GetActiveSheetIndex() for index, sheet := range f.GetSheetList() { if activeSheet == index { continue } ws, _ := f.workSheetReader(sheet) sheetViews := ws.SheetViews.SheetView for idx := range sheetViews { ws.SheetViews.SheetView[idx].TabSelected = false } } return nil } // InsertPageBreak create a page break to determine where the printed page // ends and where begins the next one by given worksheet name and cell // reference, so the content before the page break will be printed on one page // and after the page break on another. func (f *File) InsertPageBreak(sheet, cell string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } return ws.insertPageBreak(cell) } // insertPageBreak create a page break in the worksheet by specific cell // reference. func (ws *xlsxWorksheet) insertPageBreak(cell string) error { var ( row, col int err error rowBrk, colBrk = -1, -1 ) if col, row, err = CellNameToCoordinates(cell); err != nil { return err } col-- row-- if col == row && col == 0 { return err } if ws.RowBreaks == nil { ws.RowBreaks = &xlsxRowBreaks{} } if ws.ColBreaks == nil { ws.ColBreaks = &xlsxColBreaks{} } for idx, brk := range ws.RowBreaks.Brk { if brk.ID == row { rowBrk = idx } } for idx, brk := range ws.ColBreaks.Brk { if brk.ID == col { colBrk = idx } } if row != 0 && rowBrk == -1 { ws.RowBreaks.Brk = append(ws.RowBreaks.Brk, &xlsxBrk{ ID: row, Max: MaxColumns - 1, Man: true, }) ws.RowBreaks.ManualBreakCount++ } if col != 0 && colBrk == -1 { ws.ColBreaks.Brk = append(ws.ColBreaks.Brk, &xlsxBrk{ ID: col, Max: TotalRows - 1, Man: true, }) ws.ColBreaks.ManualBreakCount++ } ws.RowBreaks.Count = len(ws.RowBreaks.Brk) ws.ColBreaks.Count = len(ws.ColBreaks.Brk) return err } // RemovePageBreak remove a page break by given worksheet name and cell // reference. func (f *File) RemovePageBreak(sheet, cell string) error { var ( ws *xlsxWorksheet row, col int err error ) if ws, err = f.workSheetReader(sheet); err != nil { return err } if col, row, err = CellNameToCoordinates(cell); err != nil { return err } col-- row-- if col == row && col == 0 { return err } removeBrk := func(ID int, brks []*xlsxBrk) []*xlsxBrk { for i, brk := range brks { if brk.ID == ID { brks = append(brks[:i], brks[i+1:]...) } } return brks } if ws.RowBreaks == nil || ws.ColBreaks == nil { return err } rowBrks := len(ws.RowBreaks.Brk) colBrks := len(ws.ColBreaks.Brk) if rowBrks > 0 && rowBrks == colBrks { ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk) ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk) ws.RowBreaks.Count = len(ws.RowBreaks.Brk) ws.ColBreaks.Count = len(ws.ColBreaks.Brk) ws.RowBreaks.ManualBreakCount-- ws.ColBreaks.ManualBreakCount-- return err } if rowBrks > 0 && rowBrks > colBrks { ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk) ws.RowBreaks.Count = len(ws.RowBreaks.Brk) ws.RowBreaks.ManualBreakCount-- return err } if colBrks > 0 && colBrks > rowBrks { ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk) ws.ColBreaks.Count = len(ws.ColBreaks.Brk) ws.ColBreaks.ManualBreakCount-- } return err } // relsReader provides a function to get the pointer to the structure // after deserialization of relationships parts. func (f *File) relsReader(path string) (*xlsxRelationships, error) { rels, _ := f.Relationships.Load(path) if rels == nil { if _, ok := f.Pkg.Load(path); ok { c := xlsxRelationships{} if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). Decode(&c); err != nil && err != io.EOF { return nil, err } f.Relationships.Store(path, &c) } } if rels, _ = f.Relationships.Load(path); rels != nil { return rels.(*xlsxRelationships), nil } return nil, nil } // fillSheetData ensures there are enough rows, and columns in the chosen // row to accept data. Missing rows are backfilled and given their row number // Uses the last populated row as a hint for the size of the next row to add func (ws *xlsxWorksheet) prepareSheetXML(col, row int) { rowCount := len(ws.SheetData.Row) sizeHint := 0 var ht *float64 var customHeight bool if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight { ht = float64Ptr(ws.SheetFormatPr.DefaultRowHeight) customHeight = true } if rowCount > 0 { sizeHint = len(ws.SheetData.Row[rowCount-1].C) } if rowCount < row { // append missing rows for rowIdx := rowCount; rowIdx < row; rowIdx++ { ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)}) } } rowData := &ws.SheetData.Row[row-1] fillColumns(rowData, col, row) } // fillColumns fill cells in the column of the row as contiguous. func fillColumns(rowData *xlsxRow, col, row int) { cellCount := len(rowData.C) if cellCount < col { for colIdx := cellCount; colIdx < col; colIdx++ { cellName, _ := CoordinatesToCellName(colIdx+1, row) rowData.C = append(rowData.C, xlsxC{R: cellName}) } } } // makeContiguousColumns make columns in specific rows as contiguous. func (ws *xlsxWorksheet) makeContiguousColumns(fromRow, toRow, colCount int) { for ; fromRow < toRow; fromRow++ { rowData := &ws.SheetData.Row[fromRow-1] fillColumns(rowData, colCount, fromRow) } } // SetSheetDimension provides the method to set or remove the used range of the // worksheet by a given range reference. It specifies the row and column bounds // of used cells in the worksheet. The range reference is set using the A1 // reference style(e.g., "A1:D5"). Passing an empty range reference will remove // the used range of the worksheet. func (f *File) SetSheetDimension(sheet, rangeRef string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } // Remove the dimension element if an empty string is provided if rangeRef == "" { ws.Dimension = nil return nil } parts := len(strings.Split(rangeRef, ":")) if parts == 1 { _, _, err = CellNameToCoordinates(rangeRef) if err == nil { ws.Dimension = &xlsxDimension{Ref: strings.ToUpper(rangeRef)} } return err } if parts != 2 { return ErrParameterInvalid } coordinates, err := rangeRefToCoordinates(rangeRef) if err != nil { return err } _ = sortCoordinates(coordinates) ref, err := coordinatesToRangeRef(coordinates) ws.Dimension = &xlsxDimension{Ref: ref} return err } // GetSheetDimension provides the method to get the used range of the worksheet. func (f *File) GetSheetDimension(sheet string) (string, error) { var ref string if err := checkSheetName(sheet); err != nil { return ref, err } name, ok := f.getSheetXMLPath(sheet) if !ok { return ref, ErrSheetNotExist{sheet} } if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { ws := worksheet.(*xlsxWorksheet) if ws.Dimension != nil { ref = ws.Dimension.Ref } return ref, nil } needClose, decoder, tempFile, err := f.xmlDecoder(name) if needClose && err == nil { defer func() { err = tempFile.Close() }() } for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: if xmlElement.Name.Local == "dimension" { for _, attr := range xmlElement.Attr { if attr.Name.Local == "ref" { return attr.Value, err } } } if xmlElement.Name.Local == "sheetData" { return ref, err } } } return ref, err } // AddIgnoredErrors provides the method to ignored error for a range of cells. func (f *File) AddIgnoredErrors(sheet, rangeRef string, ignoredErrorsType IgnoredErrorsType) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if rangeRef == "" { return ErrParameterInvalid } if ws.IgnoredErrors == nil { ws.IgnoredErrors = &xlsxIgnoredErrors{} } ie := map[IgnoredErrorsType]xlsxIgnoredError{ IgnoredErrorsEvalError: {Sqref: rangeRef, EvalError: true}, IgnoredErrorsTwoDigitTextYear: {Sqref: rangeRef, TwoDigitTextYear: true}, IgnoredErrorsNumberStoredAsText: {Sqref: rangeRef, NumberStoredAsText: true}, IgnoredErrorsFormula: {Sqref: rangeRef, Formula: true}, IgnoredErrorsFormulaRange: {Sqref: rangeRef, FormulaRange: true}, IgnoredErrorsUnlockedFormula: {Sqref: rangeRef, UnlockedFormula: true}, IgnoredErrorsEmptyCellReference: {Sqref: rangeRef, EmptyCellReference: true}, IgnoredErrorsListDataValidation: {Sqref: rangeRef, ListDataValidation: true}, IgnoredErrorsCalculatedColumn: {Sqref: rangeRef, CalculatedColumn: true}, }[ignoredErrorsType] for _, val := range ws.IgnoredErrors.IgnoredError { if reflect.DeepEqual(val, ie) { return err } } ws.IgnoredErrors.IgnoredError = append(ws.IgnoredErrors.IgnoredError, ie) return err } ================================================ FILE: sheet_test.go ================================================ package excelize import ( "encoding/xml" "fmt" "io" "os" "path/filepath" "strconv" "strings" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestNewSheet(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) sheetID, err := f.NewSheet("sheet2") assert.NoError(t, err) f.SetActiveSheet(sheetID) // Test delete original sheet idx, err := f.GetSheetIndex("Sheet1") assert.NoError(t, err) assert.NoError(t, f.DeleteSheet(f.GetSheetName(idx))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx"))) // Test create new worksheet with already exists name sheetID, err = f.NewSheet("Sheet2") assert.NoError(t, err) idx, err = f.GetSheetIndex("Sheet2") assert.NoError(t, err) assert.Equal(t, idx, sheetID) // Test create new worksheet with empty sheet name sheetID, err = f.NewSheet(":\\/?*[]") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) assert.Equal(t, -1, sheetID) } func TestPanes(t *testing.T) { f := NewFile() assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false})) _, err := f.NewSheet("Panes 2") assert.NoError(t, err) expected := Panes{ Freeze: true, Split: false, XSplit: 1, YSplit: 0, TopLeftCell: "B1", ActivePane: "topRight", Selection: []Selection{ {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, }, } assert.NoError(t, f.SetPanes("Panes 2", &expected)) panes, err := f.GetPanes("Panes 2") assert.NoError(t, err) assert.Equal(t, expected, panes) _, err = f.NewSheet("Panes 3") assert.NoError(t, err) assert.NoError(t, f.SetPanes("Panes 3", &Panes{ Freeze: false, Split: true, XSplit: 3270, YSplit: 1800, TopLeftCell: "N57", ActivePane: "bottomLeft", Selection: []Selection{ {SQRef: "I36", ActiveCell: "I36"}, {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"}, {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"}, {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"}, }, }, )) _, err = f.NewSheet("Panes 4") assert.NoError(t, err) assert.NoError(t, f.SetPanes("Panes 4", &Panes{ Freeze: true, Split: false, XSplit: 0, YSplit: 9, TopLeftCell: "A34", ActivePane: "bottomLeft", Selection: []Selection{ {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"}, }, }, )) assert.EqualError(t, f.SetPanes("Panes 4", nil), ErrParameterInvalid.Error()) assert.EqualError(t, f.SetPanes("SheetN", nil), "sheet SheetN does not exist") // Test set panes with invalid sheet name assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx"))) // Test get panes with empty sheet views f = NewFile() ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{} _, err = f.GetPanes("Sheet1") assert.NoError(t, err) // Test get panes without panes ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}} _, err = f.GetPanes("Sheet1") assert.NoError(t, err) // Test get panes without sheet views ws.(*xlsxWorksheet).SheetViews = nil _, err = f.GetPanes("Sheet1") assert.NoError(t, err) // Test get panes on not exists worksheet _, err = f.GetPanes("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test add pane on empty sheet views worksheet f = NewFile() f.checked = sync.Map{} f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) assert.NoError(t, f.SetPanes("Sheet1", &Panes{ Freeze: true, Split: false, XSplit: 1, YSplit: 0, TopLeftCell: "B1", ActivePane: "topRight", Selection: []Selection{ {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, }, }, )) } func TestSearchSheet(t *testing.T) { f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx")) if !assert.NoError(t, err) { t.FailNow() } // Test search in a not exists worksheet _, err = f.SearchSheet("Sheet4", "") assert.EqualError(t, err, "sheet Sheet4 does not exist") // Test search sheet with invalid sheet name _, err = f.SearchSheet("Sheet:1", "") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) var expected []string // Test search a not exists value result, err := f.SearchSheet("Sheet1", "X") assert.NoError(t, err) assert.EqualValues(t, expected, result) result, err = f.SearchSheet("Sheet1", "A") assert.NoError(t, err) assert.EqualValues(t, []string{"A1"}, result) // Test search the coordinates where the numerical value in the range of // "0-9" of Sheet1 is described by regular expression: result, err = f.SearchSheet("Sheet1", "[0-9]", true) assert.NoError(t, err) assert.EqualValues(t, expected, result) assert.NoError(t, f.Close()) // Test search worksheet data after set cell value f = NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", true)) _, err = f.SearchSheet("Sheet1", "") assert.NoError(t, err) f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A`)) f.checked = sync.Map{} result, err = f.SearchSheet("Sheet1", "A") assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax") assert.Equal(t, []string(nil), result) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A`)) result, err = f.SearchSheet("Sheet1", "A") assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) assert.Equal(t, []string(nil), result) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A`)) result, err = f.SearchSheet("Sheet1", "A") assert.Equal(t, newCoordinatesToCellNameError(1, 0), err) assert.Equal(t, []string(nil), result) // Test search sheet with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err = f.SearchSheet("Sheet1", "A") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetPageLayout(t *testing.T) { f := NewFile() assert.NoError(t, f.SetPageLayout("Sheet1", nil)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).PageSetUp = nil expected := PageLayoutOptions{ Size: intPtr(1), Orientation: stringPtr("landscape"), FirstPageNumber: uintPtr(1), AdjustTo: uintPtr(120), FitToHeight: intPtr(2), FitToWidth: intPtr(2), BlackAndWhite: boolPtr(true), PageOrder: stringPtr("overThenDown"), } assert.NoError(t, f.SetPageLayout("Sheet1", &expected)) opts, err := f.GetPageLayout("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, opts) // Test set page layout on not exists worksheet assert.EqualError(t, f.SetPageLayout("SheetN", nil), "sheet SheetN does not exist") // Test set page layout with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetPageLayout("Sheet:1", nil)) // Test set page layout with invalid parameters assert.Equal(t, ErrPageSetupAdjustTo, f.SetPageLayout("Sheet1", &PageLayoutOptions{ AdjustTo: uintPtr(5), })) assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{ Orientation: stringPtr("x"), }), "invalid Orientation value \"x\", acceptable value should be one of portrait, landscape") assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{ PageOrder: stringPtr("x"), }), "invalid PageOrder value \"x\", acceptable value should be one of overThenDown, downThenOver") } func TestGetPageLayout(t *testing.T) { f := NewFile() // Test get page layout on not exists worksheet _, err := f.GetPageLayout("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get page layout with invalid sheet name _, err = f.GetPageLayout("Sheet:1") assert.EqualError(t, err, ErrSheetNameInvalid.Error()) } func TestHeaderFooter(t *testing.T) { f := NewFile() // Test get header and footer with default header and footer settings opts, err := f.GetHeaderFooter("Sheet1") assert.NoError(t, err) assert.Equal(t, (*HeaderFooterOptions)(nil), opts) // Test get header and footer on not exists worksheet _, err = f.GetHeaderFooter("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter")) // Test set header and footer on not exists worksheet assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN does not exist") // Test Sheet:1 with invalid sheet name assert.EqualError(t, f.SetHeaderFooter("Sheet:1", nil), ErrSheetNameInvalid.Error()) // Test set header and footer with illegal setting assert.EqualError(t, f.SetHeaderFooter("Sheet1", &HeaderFooterOptions{ OddHeader: strings.Repeat("c", MaxFieldLength+1), }), newFieldLengthError("OddHeader").Error()) assert.NoError(t, f.SetHeaderFooter("Sheet1", nil)) text := strings.Repeat("一", MaxFieldLength) assert.NoError(t, f.SetHeaderFooter("Sheet1", &HeaderFooterOptions{ OddHeader: text, OddFooter: text, EvenHeader: text, EvenFooter: text, FirstHeader: text, })) expected := &HeaderFooterOptions{ DifferentFirst: true, DifferentOddEven: true, OddHeader: "&R&P", OddFooter: "&C&F", EvenHeader: "&L&P", EvenFooter: "&L&D&R&T", FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`, } assert.NoError(t, f.SetHeaderFooter("Sheet1", expected)) opts, err = f.GetHeaderFooter("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, opts) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetHeaderFooter.xlsx"))) } func TestDefinedName(t *testing.T) { f := NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Amount.", RefersTo: "Sheet1!$A$2:$D$5", Comment: "defined name comment", Scope: "Sheet1", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5", Comment: "defined name comment", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: builtInDefinedNames[0], RefersTo: "Sheet1!$A$1:$Z$100", Scope: "Sheet1", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: builtInDefinedNames[1], RefersTo: "Sheet1!$A:$A,Sheet1!$1:$1", Scope: "Sheet1", })) assert.EqualError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5", Comment: "defined name comment", }), ErrDefinedNameDuplicate.Error()) assert.EqualError(t, f.DeleteDefinedName(&DefinedName{ Name: "No Exist Defined Name", }), ErrDefinedNameScope.Error()) // Test set defined name without name assert.EqualError(t, f.SetDefinedName(&DefinedName{ RefersTo: "Sheet1!$A$2:$D$5", }), ErrParameterInvalid.Error()) // Test set defined name without reference assert.EqualError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", }), ErrParameterInvalid.Error()) assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo) assert.NoError(t, f.DeleteDefinedName(&DefinedName{ Name: "Amount", })) assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo) assert.Len(t, f.GetDefinedName(), 3) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx"))) // Test set defined name with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5", }), "XML syntax error on line 1: invalid UTF-8") // Test delete defined name with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.DeleteDefinedName(&DefinedName{Name: "Amount"}), "XML syntax error on line 1: invalid UTF-8") } func TestGroupSheets(t *testing.T) { f := NewFile() sheets := []string{"Sheet2", "Sheet3"} for _, sheet := range sheets { _, err := f.NewSheet(sheet) assert.NoError(t, err) } assert.EqualError(t, f.GroupSheets([]string{"Sheet1", "SheetN"}), "sheet SheetN does not exist") assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet") // Test group sheets with invalid sheet name assert.EqualError(t, f.GroupSheets([]string{"Sheet:1", "Sheet1"}), ErrSheetNameInvalid.Error()) assert.NoError(t, f.GroupSheets([]string{"Sheet1", "Sheet2"})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGroupSheets.xlsx"))) } func TestUngroupSheets(t *testing.T) { f := NewFile() sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"} for _, sheet := range sheets { _, err := f.NewSheet(sheet) assert.NoError(t, err) } assert.NoError(t, f.UngroupSheets()) } func TestInsertPageBreak(t *testing.T) { f := NewFile() assert.NoError(t, f.InsertPageBreak("Sheet1", "A1")) assert.NoError(t, f.InsertPageBreak("Sheet1", "B2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "C3")) assert.NoError(t, f.InsertPageBreak("Sheet1", "C3")) assert.EqualError(t, f.InsertPageBreak("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.InsertPageBreak("SheetN", "C3"), "sheet SheetN does not exist") // Test insert page break with invalid sheet name assert.EqualError(t, f.InsertPageBreak("Sheet:1", "C3"), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertPageBreak.xlsx"))) } func TestRemovePageBreak(t *testing.T) { f := NewFile() assert.NoError(t, f.RemovePageBreak("Sheet1", "A2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "A2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "B2")) assert.NoError(t, f.RemovePageBreak("Sheet1", "A1")) assert.NoError(t, f.RemovePageBreak("Sheet1", "B2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "C3")) assert.NoError(t, f.RemovePageBreak("Sheet1", "C3")) assert.NoError(t, f.InsertPageBreak("Sheet1", "A3")) assert.NoError(t, f.RemovePageBreak("Sheet1", "B3")) assert.NoError(t, f.RemovePageBreak("Sheet1", "A3")) _, err := f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.InsertPageBreak("Sheet2", "B2")) assert.NoError(t, f.InsertPageBreak("Sheet2", "C2")) assert.NoError(t, f.RemovePageBreak("Sheet2", "B2")) assert.EqualError(t, f.RemovePageBreak("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.RemovePageBreak("SheetN", "C3"), "sheet SheetN does not exist") // Test remove page break with invalid sheet name assert.EqualError(t, f.RemovePageBreak("Sheet:1", "A3"), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemovePageBreak.xlsx"))) } func TestGetSheetName(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.Equal(t, "Sheet1", f.GetSheetName(0)) assert.Equal(t, "Sheet2", f.GetSheetName(1)) assert.Empty(t, f.GetSheetName(-1)) assert.Empty(t, f.GetSheetName(2)) assert.NoError(t, f.Close()) } func TestGetSheetMap(t *testing.T) { expectedMap := map[int]string{ 1: "Sheet1", 2: "Sheet2", } f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) sheetMap := f.GetSheetMap() for idx, name := range sheetMap { assert.Equal(t, expectedMap[idx], name) } assert.Len(t, sheetMap, 2) assert.NoError(t, f.Close()) f = NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err = f.getSheetMap() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetActiveSheet(t *testing.T) { f := NewFile() f.WorkBook.BookViews = nil f.SetActiveSheet(1) f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}} ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}} f.SetActiveSheet(1) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = nil f.SetActiveSheet(1) f = NewFile() f.SetActiveSheet(-1) assert.Equal(t, f.GetActiveSheetIndex(), 0) f = NewFile() f.WorkBook.BookViews = nil idx, err := f.NewSheet("Sheet2") assert.NoError(t, err) ws, ok = f.Sheet.Load("xl/worksheets/sheet2.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}} f.SetActiveSheet(idx) } func TestSetSheetName(t *testing.T) { f := NewFile() // Test set worksheet with the same name assert.NoError(t, f.SetSheetName("Sheet1", "Sheet1")) assert.Equal(t, "Sheet1", f.GetSheetName(0)) // Test set worksheet with the different name assert.NoError(t, f.SetSheetName("Sheet1", "sheet1")) assert.Equal(t, "sheet1", f.GetSheetName(0)) // Test set sheet name with invalid sheet name assert.Equal(t, f.SetSheetName("Sheet:1", "Sheet1"), ErrSheetNameInvalid) _, err := f.NewSheet("Sheet 3") assert.NoError(t, err) // Test set worksheet name with existing defined name and auto filter assert.NoError(t, f.AutoFilter("Sheet1", "A1:A2", nil)) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name1", RefersTo: "$B$2", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name2", RefersTo: "$A1$2:A2", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name3", RefersTo: "Sheet1!$A$1:'Sheet1'!A1:Sheet1!$A$1,Sheet1!A1:Sheet3!A1,Sheet3!A1", })) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Name4", RefersTo: "'Sheet 3'!$A1$2:A2", })) assert.NoError(t, f.SetSheetName("Sheet1", "Sheet 2")) for i, expected := range []string{"'Sheet 2'!$A$1:$A$2", "$B$2", "$A1$2:A2", "'Sheet 2'!$A$1:'Sheet 2'!A1:'Sheet 2'!$A$1,'Sheet 2'!A1:Sheet3!A1,Sheet3!A1", "'Sheet 3'!$A1$2:A2"} { assert.Equal(t, expected, f.WorkBook.DefinedNames.DefinedName[i].Data) } } func TestWorksheetWriter(t *testing.T) { f := NewFile() // Test set cell value with alternate content f.Sheet.Delete("xl/worksheets/sheet1.xml") worksheet := xml.Header + `%d` f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(worksheet, 1))) f.checked = sync.Map{} assert.NoError(t, f.SetCellValue("Sheet1", "A1", 2)) f.workSheetWriter() value, ok := f.Pkg.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) assert.Equal(t, fmt.Sprintf(worksheet, 2), string(value.([]byte))) } func TestGetWorkbookPath(t *testing.T) { f := NewFile() f.Pkg.Delete(defaultXMLPathRels) assert.Empty(t, f.getWorkbookPath()) } func TestGetWorkbookRelsPath(t *testing.T) { f := NewFile() f.Pkg.Delete("xl/_rels/.rels") f.Pkg.Store(defaultXMLPathRels, []byte(xml.Header+``)) assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath()) } func TestDeleteSheet(t *testing.T) { f := NewFile() idx, err := f.NewSheet("Sheet2") assert.NoError(t, err) f.SetActiveSheet(idx) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) assert.NoError(t, f.DeleteSheet("Sheet1")) assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex())) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx"))) // Test with auto filter defined names f = NewFile() _, err = f.NewSheet("Sheet2") assert.NoError(t, err) _, err = f.NewSheet("Sheet3") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A")) assert.NoError(t, f.AutoFilter("Sheet1", "A1:A1", nil)) assert.NoError(t, f.AutoFilter("Sheet2", "A1:A1", nil)) assert.NoError(t, f.AutoFilter("Sheet3", "A1:A1", nil)) assert.NoError(t, f.DeleteSheet("Sheet2")) assert.NoError(t, f.DeleteSheet("Sheet1")) // Test delete sheet with invalid sheet name assert.EqualError(t, f.DeleteSheet("Sheet:1"), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx"))) } func TestMoveSheet(t *testing.T) { f := NewFile() defer func() { assert.NoError(t, f.Close()) }() for i := 2; i < 6; i++ { _, err := f.NewSheet("Sheet" + strconv.Itoa(i)) assert.NoError(t, err) } assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList()) // Move target to first position assert.NoError(t, f.MoveSheet("Sheet2", "Sheet1")) assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList()) assert.Equal(t, "Sheet1", f.GetSheetName(f.GetActiveSheetIndex())) // Move target to last position assert.NoError(t, f.MoveSheet("Sheet2", "Sheet5")) assert.NoError(t, f.MoveSheet("Sheet5", "Sheet2")) assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList()) // Move target to same position assert.NoError(t, f.MoveSheet("Sheet1", "Sheet1")) assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList()) // Test move sheet with invalid sheet name assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("", "Sheet2")) assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("Sheet1", "")) // Test move sheet on not exists worksheet assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("SheetN", "Sheet2")) assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("Sheet1", "SheetN")) // Test move sheet with unsupported workbook charset f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.MoveSheet("Sheet2", "Sheet1"), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteAndAdjustDefinedNames(t *testing.T) { deleteAndAdjustDefinedNames(nil, 0) deleteAndAdjustDefinedNames(&xlsxWorkbook{}, 0) } func TestGetSheetID(t *testing.T) { f := NewFile() _, err := f.NewSheet("Sheet1") assert.NoError(t, err) id := f.getSheetID("sheet1") assert.NotEqual(t, -1, id) } func TestSetSheetVisible(t *testing.T) { f := NewFile() // Test set sheet visible with invalid sheet name assert.EqualError(t, f.SetSheetVisible("Sheet:1", false), ErrSheetNameInvalid.Error()) f.WorkBook.Sheets.Sheet[0].Name = "SheetN" assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist") // Test set sheet visible with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "XML syntax error on line 1: invalid UTF-8") // Test set sheet visible with empty sheet views f = NewFile() _, err := f.NewSheet("Sheet2") assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet2.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = nil assert.NoError(t, f.SetSheetVisible("Sheet2", false)) visible, err := f.GetSheetVisible("Sheet2") assert.NoError(t, err) assert.False(t, visible) } func TestGetSheetVisible(t *testing.T) { f := NewFile() // Test get sheet visible with invalid sheet name visible, err := f.GetSheetVisible("Sheet:1") assert.Equal(t, false, visible) assert.EqualError(t, err, ErrSheetNameInvalid.Error()) } func TestGetSheetIndex(t *testing.T) { f := NewFile() // Test get sheet index with invalid sheet name idx, err := f.GetSheetIndex("Sheet:1") assert.Equal(t, -1, idx) assert.EqualError(t, err, ErrSheetNameInvalid.Error()) } func TestSetContentTypes(t *testing.T) { f := NewFile() // Test set content type with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8") } func TestRemoveContentTypesPart(t *testing.T) { f := NewFile() // Test delete sheet from content types with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, "/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") } func BenchmarkNewSheet(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { newSheetWithSet() } }) } func newSheetWithSet() { file := NewFile() for i := 0; i < 1000; i++ { _ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), int64(i)) } file = nil } func BenchmarkFile_SaveAs(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { newSheetWithSave() } }) } func newSheetWithSave() { file := NewFile() for i := 0; i < 1000; i++ { _ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), int64(i)) } _ = file.Save() } func TestAttrValToBool(t *testing.T) { _, err := attrValToBool("hidden", []xml.Attr{ {Name: xml.Name{Local: "hidden"}}, }) assert.EqualError(t, err, `strconv.ParseBool: parsing "": invalid syntax`) got, err := attrValToBool("hidden", []xml.Attr{ {Name: xml.Name{Local: "hidden"}, Value: "1"}, }) assert.NoError(t, err) assert.Equal(t, true, got) } func TestAttrValToFloat(t *testing.T) { _, err := attrValToFloat("ht", []xml.Attr{ {Name: xml.Name{Local: "ht"}}, }) assert.EqualError(t, err, `strconv.ParseFloat: parsing "": invalid syntax`) got, err := attrValToFloat("ht", []xml.Attr{ {Name: xml.Name{Local: "ht"}, Value: "42.1"}, }) assert.NoError(t, err) assert.Equal(t, 42.1, got) } func TestSetSheetBackgroundFromBytes(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetName("Sheet1", ".svg")) for i, imageTypes := range []string{ ".svg", ".bmp", ".emf", ".emz", ".gif", ".jpg", ".png", ".tif", ".wmf", ".wmz", } { file := fmt.Sprintf("excelize%s", imageTypes) if i > 0 { file = filepath.Join("test", "images", fmt.Sprintf("excel%s", imageTypes)) _, err := f.NewSheet(imageTypes) assert.NoError(t, err) } img, err := os.Open(file) assert.NoError(t, err) content, err := io.ReadAll(img) assert.NoError(t, err) assert.NoError(t, img.Close()) assert.NoError(t, f.SetSheetBackgroundFromBytes(imageTypes, imageTypes, content)) } // Test set worksheet background with invalid sheet name img, err := os.Open(filepath.Join("test", "images", "excel.png")) assert.NoError(t, err) content, err := io.ReadAll(img) assert.NoError(t, err) assert.EqualError(t, f.SetSheetBackgroundFromBytes("Sheet:1", ".png", content), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackgroundFromBytes.xlsx"))) assert.NoError(t, f.Close()) assert.EqualError(t, f.SetSheetBackgroundFromBytes("Sheet1", ".svg", nil), ErrParameterInvalid.Error()) } func TestCheckSheetName(t *testing.T) { for expected, name := range map[error]string{ // Test valid sheet name nil: "Sheet1", nil: "She'et1", // Test invalid sheet name, empty name ErrSheetNameBlank: "", // Test invalid sheet name, include :\/?*[] ErrSheetNameInvalid: "Sheet:", ErrSheetNameInvalid: `Sheet\`, ErrSheetNameInvalid: "Sheet/", ErrSheetNameInvalid: "Sheet?", ErrSheetNameInvalid: "Sheet*", ErrSheetNameInvalid: "Sheet[", ErrSheetNameInvalid: "Sheet]", // Test invalid sheet name, single quotes at the front or at the end ErrSheetNameSingleQuote: "'Sheet", ErrSheetNameSingleQuote: "Sheet'", // Test invalid sheet name, exceed max length ErrSheetNameLength: "Sheet" + strings.Repeat("\U0001F600", 14), } { assert.Equal(t, expected, checkSheetName(name)) } } func TestSheetDimension(t *testing.T) { f := NewFile() const sheetName = "Sheet1" // Test get a new worksheet dimension dimension, err := f.GetSheetDimension(sheetName) assert.NoError(t, err) assert.Equal(t, "A1", dimension) // Test remove the worksheet dimension assert.NoError(t, f.SetSheetDimension(sheetName, "")) assert.NoError(t, err) dimension, err = f.GetSheetDimension(sheetName) assert.NoError(t, err) assert.Empty(t, dimension) // Test set the worksheet dimension for _, excepted := range []string{"A1", "A1:D5", "A1:XFD1048576", "a1", "A1:d5"} { err = f.SetSheetDimension(sheetName, excepted) assert.NoError(t, err) dimension, err := f.GetSheetDimension(sheetName) assert.NoError(t, err) assert.Equal(t, strings.ToUpper(excepted), dimension) } // Test set the worksheet dimension with invalid range reference or no exists worksheet for _, c := range []struct { sheetName string rangeRef string err string }{ {"Sheet1", "A-1", "cannot convert cell \"A-1\" to coordinates: invalid cell name \"A-1\""}, {"Sheet1", "A1:B-1", "cannot convert cell \"B-1\" to coordinates: invalid cell name \"B-1\""}, {"Sheet1", "A1:XFD1048577", "row number exceeds maximum limit"}, {"Sheet1", "123", "cannot convert cell \"123\" to coordinates: invalid cell name \"123\""}, {"Sheet1", "A:B", "cannot convert cell \"A\" to coordinates: invalid cell name \"A\""}, {"Sheet1", ":B10", "cannot convert cell \"\" to coordinates: invalid cell name \"\""}, {"Sheet1", "XFE1", ErrColumnNumber.Error()}, {"Sheet1", "A1048577", "row number exceeds maximum limit"}, {"Sheet1", "ZZZ", "cannot convert cell \"ZZZ\" to coordinates: invalid cell name \"ZZZ\""}, {"SheetN", "A1", "sheet SheetN does not exist"}, {"Sheet1", "A1:B3:D5", ErrParameterInvalid.Error()}, } { err = f.SetSheetDimension(c.sheetName, c.rangeRef) assert.EqualError(t, err, c.err) } // Test get the worksheet dimension no exists worksheet dimension, err = f.GetSheetDimension("SheetN") assert.Empty(t, dimension) assert.EqualError(t, err, "sheet SheetN does not exist") // Test get the worksheet dimension with blank worksheet name dimension, err = f.GetSheetDimension("") assert.Empty(t, dimension) assert.Equal(t, err, ErrSheetNameBlank) // Test get the worksheet dimension with in mode f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) dimension, err = f.GetSheetDimension("Sheet1") assert.Equal(t, "A19:D22", dimension) assert.NoError(t, err) assert.NoError(t, f.Close()) // Test get the worksheet dimension in stream mode without dimension element f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128}) assert.NoError(t, err) tempFile, ok := f.tempFiles.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) assert.NoError(t, os.WriteFile(tempFile.(string), fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value), 0o644)) dimension, err = f.GetSheetDimension("Sheet1") assert.NoError(t, err) assert.Empty(t, dimension) // Test get the worksheet dimension in stream mode without sheetData element assert.NoError(t, os.WriteFile(tempFile.(string), fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value), 0o644)) dimension, err = f.GetSheetDimension("Sheet1") assert.NoError(t, err) assert.Empty(t, dimension) assert.NoError(t, f.Close()) } func TestAddIgnoredErrors(t *testing.T) { f := NewFile() assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEvalError)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEvalError)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsTwoDigitTextYear)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsNumberStoredAsText)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsFormula)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsFormulaRange)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsUnlockedFormula)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEmptyCellReference)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsListDataValidation)) assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsCalculatedColumn)) assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddIgnoredErrors("SheetN", "A1", IgnoredErrorsEvalError)) assert.Equal(t, ErrParameterInvalid, f.AddIgnoredErrors("Sheet1", "", IgnoredErrorsEvalError)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddIgnoredErrors.xlsx"))) assert.NoError(t, f.Close()) } ================================================ FILE: sheetpr.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "reflect" // SetPageMargins provides a function to set worksheet page margins. func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if opts == nil { return err } preparePageMargins := func(ws *xlsxWorksheet) { if ws.PageMargins == nil { ws.PageMargins = new(xlsxPageMargins) } } preparePrintOptions := func(ws *xlsxWorksheet) { if ws.PrintOptions == nil { ws.PrintOptions = new(xlsxPrintOptions) } } s := reflect.ValueOf(opts).Elem() for i := 0; i < 6; i++ { if !s.Field(i).IsNil() { preparePageMargins(ws) name := s.Type().Field(i).Name reflect.ValueOf(ws.PageMargins).Elem().FieldByName(name).Set(s.Field(i).Elem()) } } if opts.Horizontally != nil { preparePrintOptions(ws) ws.PrintOptions.HorizontalCentered = *opts.Horizontally } if opts.Vertically != nil { preparePrintOptions(ws) ws.PrintOptions.VerticalCentered = *opts.Vertically } return err } // GetPageMargins provides a function to get worksheet page margins. func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) { opts := PageLayoutMarginsOptions{ Bottom: float64Ptr(0.75), Footer: float64Ptr(0.3), Header: float64Ptr(0.3), Left: float64Ptr(0.7), Right: float64Ptr(0.7), Top: float64Ptr(0.75), } ws, err := f.workSheetReader(sheet) if err != nil { return opts, err } if ws.PageMargins != nil { opts.Bottom = float64Ptr(ws.PageMargins.Bottom) opts.Footer = float64Ptr(ws.PageMargins.Footer) opts.Header = float64Ptr(ws.PageMargins.Header) opts.Left = float64Ptr(ws.PageMargins.Left) opts.Right = float64Ptr(ws.PageMargins.Right) opts.Top = float64Ptr(ws.PageMargins.Top) } if ws.PrintOptions != nil { opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered) opts.Vertically = boolPtr(ws.PrintOptions.VerticalCentered) } return opts, err } // prepareSheetPr create sheetPr element which not exist. func (ws *xlsxWorksheet) prepareSheetPr() { if ws.SheetPr == nil { ws.SheetPr = new(xlsxSheetPr) } } // setSheetOutlineProps set worksheet outline properties by given options. func (ws *xlsxWorksheet) setSheetOutlineProps(opts *SheetPropsOptions) { prepareOutlinePr := func(ws *xlsxWorksheet) { ws.prepareSheetPr() if ws.SheetPr.OutlinePr == nil { ws.SheetPr.OutlinePr = new(xlsxOutlinePr) } } if opts.OutlineSummaryBelow != nil { prepareOutlinePr(ws) ws.SheetPr.OutlinePr.SummaryBelow = opts.OutlineSummaryBelow } if opts.OutlineSummaryRight != nil { prepareOutlinePr(ws) ws.SheetPr.OutlinePr.SummaryRight = opts.OutlineSummaryRight } } // setSheetProps set worksheet format properties by given options. func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) { preparePageSetUpPr := func(ws *xlsxWorksheet) { ws.prepareSheetPr() if ws.SheetPr.PageSetUpPr == nil { ws.SheetPr.PageSetUpPr = new(xlsxPageSetUpPr) } } prepareTabColor := func(ws *xlsxWorksheet) { ws.prepareSheetPr() if ws.SheetPr.TabColor == nil { ws.SheetPr.TabColor = new(xlsxColor) } } if opts.CodeName != nil { ws.prepareSheetPr() ws.SheetPr.CodeName = *opts.CodeName } if opts.EnableFormatConditionsCalculation != nil { ws.prepareSheetPr() ws.SheetPr.EnableFormatConditionsCalculation = opts.EnableFormatConditionsCalculation } if opts.Published != nil { ws.prepareSheetPr() ws.SheetPr.Published = opts.Published } if opts.AutoPageBreaks != nil { preparePageSetUpPr(ws) ws.SheetPr.PageSetUpPr.AutoPageBreaks = *opts.AutoPageBreaks } if opts.FitToPage != nil { preparePageSetUpPr(ws) ws.SheetPr.PageSetUpPr.FitToPage = *opts.FitToPage } ws.setSheetOutlineProps(opts) s := reflect.ValueOf(opts).Elem() for i := 5; i < 9; i++ { if !s.Field(i).IsNil() { prepareTabColor(ws) name := s.Type().Field(i).Name fld := reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:]) if s.Field(i).Kind() == reflect.Ptr && fld.Kind() == reflect.Ptr { fld.Set(s.Field(i)) continue } fld.Set(s.Field(i).Elem()) } } } // SetSheetProps provides a function to set worksheet properties. func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if opts == nil { return err } ws.setSheetProps(opts) if ws.SheetFormatPr == nil { ws.SheetFormatPr = &xlsxSheetFormatPr{DefaultRowHeight: defaultRowHeight} } s := reflect.ValueOf(opts).Elem() for i := 11; i < 18; i++ { if !s.Field(i).IsNil() { name := s.Type().Field(i).Name reflect.ValueOf(ws.SheetFormatPr).Elem().FieldByName(name).Set(s.Field(i).Elem()) } } return err } // GetSheetProps provides a function to get worksheet properties. func (f *File) GetSheetProps(sheet string) (SheetPropsOptions, error) { baseColWidth := uint8(8) opts := SheetPropsOptions{ EnableFormatConditionsCalculation: boolPtr(true), Published: boolPtr(true), AutoPageBreaks: boolPtr(true), OutlineSummaryBelow: boolPtr(true), BaseColWidth: &baseColWidth, } ws, err := f.workSheetReader(sheet) if err != nil { return opts, err } if ws.SheetPr != nil { opts.CodeName = stringPtr(ws.SheetPr.CodeName) if ws.SheetPr.EnableFormatConditionsCalculation != nil { opts.EnableFormatConditionsCalculation = ws.SheetPr.EnableFormatConditionsCalculation } if ws.SheetPr.Published != nil { opts.Published = ws.SheetPr.Published } if ws.SheetPr.PageSetUpPr != nil { opts.AutoPageBreaks = boolPtr(ws.SheetPr.PageSetUpPr.AutoPageBreaks) opts.FitToPage = boolPtr(ws.SheetPr.PageSetUpPr.FitToPage) } if ws.SheetPr.OutlinePr != nil { opts.OutlineSummaryBelow = ws.SheetPr.OutlinePr.SummaryBelow opts.OutlineSummaryRight = ws.SheetPr.OutlinePr.SummaryRight } if ws.SheetPr.TabColor != nil { opts.TabColorIndexed = intPtr(ws.SheetPr.TabColor.Indexed) opts.TabColorRGB = stringPtr(ws.SheetPr.TabColor.RGB) opts.TabColorTheme = ws.SheetPr.TabColor.Theme opts.TabColorTint = float64Ptr(ws.SheetPr.TabColor.Tint) } } if ws.SheetFormatPr != nil { opts.BaseColWidth = &ws.SheetFormatPr.BaseColWidth opts.DefaultColWidth = float64Ptr(ws.SheetFormatPr.DefaultColWidth) opts.DefaultRowHeight = float64Ptr(ws.SheetFormatPr.DefaultRowHeight) opts.CustomHeight = boolPtr(ws.SheetFormatPr.CustomHeight) opts.ZeroHeight = boolPtr(ws.SheetFormatPr.ZeroHeight) opts.ThickTop = boolPtr(ws.SheetFormatPr.ThickTop) opts.ThickBottom = boolPtr(ws.SheetFormatPr.ThickBottom) } return opts, err } ================================================ FILE: sheetpr_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" ) func TestSetPageMargins(t *testing.T) { f := NewFile() assert.NoError(t, f.SetPageMargins("Sheet1", nil)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).PageMargins = nil ws.(*xlsxWorksheet).PrintOptions = nil expected := PageLayoutMarginsOptions{ Bottom: float64Ptr(1.0), Footer: float64Ptr(1.0), Header: float64Ptr(1.0), Left: float64Ptr(1.0), Right: float64Ptr(1.0), Top: float64Ptr(1.0), Horizontally: boolPtr(true), Vertically: boolPtr(true), } assert.NoError(t, f.SetPageMargins("Sheet1", &expected)) opts, err := f.GetPageMargins("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, opts) // Test set page margins on not exists worksheet assert.EqualError(t, f.SetPageMargins("SheetN", nil), "sheet SheetN does not exist") // Test set page margins with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetPageMargins("Sheet:1", nil)) } func TestGetPageMargins(t *testing.T) { f := NewFile() // Test get page margins on not exists worksheet _, err := f.GetPageMargins("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get page margins with invalid sheet name _, err = f.GetPageMargins("Sheet:1") assert.Equal(t, ErrSheetNameInvalid, err) } func TestSetSheetProps(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetProps("Sheet1", nil)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetPr = nil ws.(*xlsxWorksheet).SheetFormatPr = nil baseColWidth, enable := uint8(8), boolPtr(true) expected := SheetPropsOptions{ CodeName: stringPtr("code"), EnableFormatConditionsCalculation: enable, Published: enable, AutoPageBreaks: enable, FitToPage: enable, TabColorIndexed: intPtr(1), TabColorRGB: stringPtr("FFFF00"), TabColorTheme: intPtr(1), TabColorTint: float64Ptr(1), OutlineSummaryBelow: enable, OutlineSummaryRight: enable, BaseColWidth: &baseColWidth, DefaultColWidth: float64Ptr(10), DefaultRowHeight: float64Ptr(10), CustomHeight: enable, ZeroHeight: enable, ThickTop: enable, ThickBottom: enable, } assert.NoError(t, f.SetSheetProps("Sheet1", &expected)) opts, err := f.GetSheetProps("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, opts) ws.(*xlsxWorksheet).SheetPr = nil assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{FitToPage: enable})) ws.(*xlsxWorksheet).SheetPr = nil assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorRGB: stringPtr("FFFF00")})) ws.(*xlsxWorksheet).SheetPr = nil assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTheme: intPtr(1)})) ws.(*xlsxWorksheet).SheetPr = nil assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTint: float64Ptr(1)})) // Test get column width after set base column width _, err = f.NewSheet("Sheet2") assert.NoError(t, err) assert.NoError(t, f.SetSheetProps("Sheet2", &SheetPropsOptions{BaseColWidth: &baseColWidth})) width, err := f.GetColWidth("Sheet2", "A") assert.NoError(t, err) assert.Equal(t, 8.0, width) // Test set worksheet properties on not exists worksheet assert.EqualError(t, f.SetSheetProps("SheetN", nil), "sheet SheetN does not exist") // Test set worksheet properties with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.SetSheetProps("Sheet:1", nil)) } func TestGetSheetProps(t *testing.T) { f := NewFile() // Test get worksheet properties on not exists worksheet _, err := f.GetSheetProps("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get worksheet properties with invalid sheet name _, err = f.GetSheetProps("Sheet:1") assert.Equal(t, ErrSheetNameInvalid, err) } ================================================ FILE: sheetview.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize // getSheetView returns the SheetView object func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) { ws, err := f.workSheetReader(sheet) if err != nil { return nil, err } if ws.SheetViews == nil { ws.SheetViews = &xlsxSheetViews{ SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, } } if viewIndex < 0 { if viewIndex < -len(ws.SheetViews.SheetView) { return nil, newViewIdxError(viewIndex) } viewIndex = len(ws.SheetViews.SheetView) + viewIndex } else if viewIndex >= len(ws.SheetViews.SheetView) { return nil, newViewIdxError(viewIndex) } return &(ws.SheetViews.SheetView[viewIndex]), err } // setSheetView set sheet view by given options. func (view *xlsxSheetView) setSheetView(opts *ViewOptions) { if opts.DefaultGridColor != nil { view.DefaultGridColor = opts.DefaultGridColor } if opts.RightToLeft != nil { view.RightToLeft = *opts.RightToLeft } if opts.ShowFormulas != nil { view.ShowFormulas = *opts.ShowFormulas } if opts.ShowGridLines != nil { view.ShowGridLines = opts.ShowGridLines } if opts.ShowRowColHeaders != nil { view.ShowRowColHeaders = opts.ShowRowColHeaders } if opts.ShowRuler != nil { view.ShowRuler = opts.ShowRuler } if opts.ShowZeros != nil { view.ShowZeros = opts.ShowZeros } if opts.TopLeftCell != nil { view.TopLeftCell = *opts.TopLeftCell } if opts.View != nil { if inStrSlice([]string{"normal", "pageLayout", "pageBreakPreview"}, *opts.View, true) != -1 { view.View = *opts.View } } if opts.ZoomScale != nil && *opts.ZoomScale >= 10 && *opts.ZoomScale <= 400 { view.ZoomScale = *opts.ZoomScale } } // SetSheetView sets sheet view options. The viewIndex may be negative and if // so is counted backward (-1 is the last view). func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error { view, err := f.getSheetView(sheet, viewIndex) if err != nil { return err } if opts == nil { return err } view.setSheetView(opts) return nil } // GetSheetView gets the value of sheet view options. The viewIndex may be // negative and if so is counted backward (-1 is the last view). func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error) { opts := ViewOptions{ DefaultGridColor: boolPtr(true), ShowFormulas: boolPtr(true), ShowGridLines: boolPtr(true), ShowRowColHeaders: boolPtr(true), ShowRuler: boolPtr(true), ShowZeros: boolPtr(true), View: stringPtr("normal"), ZoomScale: float64Ptr(100), } view, err := f.getSheetView(sheet, viewIndex) if err != nil { return opts, err } if view.DefaultGridColor != nil { opts.DefaultGridColor = view.DefaultGridColor } opts.RightToLeft = boolPtr(view.RightToLeft) opts.ShowFormulas = boolPtr(view.ShowFormulas) if view.ShowGridLines != nil { opts.ShowGridLines = view.ShowGridLines } if view.ShowRowColHeaders != nil { opts.ShowRowColHeaders = view.ShowRowColHeaders } if view.ShowRuler != nil { opts.ShowRuler = view.ShowRuler } if view.ShowZeros != nil { opts.ShowZeros = view.ShowZeros } opts.TopLeftCell = stringPtr(view.TopLeftCell) if view.View != "" { opts.View = stringPtr(view.View) } if view.ZoomScale >= 10 && view.ZoomScale <= 400 { opts.ZoomScale = float64Ptr(view.ZoomScale) } return opts, err } ================================================ FILE: sheetview_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" ) func TestSetView(t *testing.T) { f := NewFile() assert.NoError(t, f.SetSheetView("Sheet1", -1, nil)) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = nil expected := ViewOptions{ DefaultGridColor: boolPtr(false), RightToLeft: boolPtr(false), ShowFormulas: boolPtr(false), ShowGridLines: boolPtr(false), ShowRowColHeaders: boolPtr(false), ShowRuler: boolPtr(false), ShowZeros: boolPtr(false), TopLeftCell: stringPtr("A1"), View: stringPtr("normal"), ZoomScale: float64Ptr(120), } assert.NoError(t, f.SetSheetView("Sheet1", 0, &expected)) opts, err := f.GetSheetView("Sheet1", 0) assert.NoError(t, err) assert.Equal(t, expected, opts) // Test set sheet view options with invalid view index assert.EqualError(t, f.SetSheetView("Sheet1", 1, nil), "view index 1 out of range") assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 out of range") // Test set sheet view options on not exists worksheet assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist") } func TestGetView(t *testing.T) { f := NewFile() _, err := f.getSheetView("SheetN", 0) assert.EqualError(t, err, "sheet SheetN does not exist") // Test get sheet view options with invalid view index _, err = f.GetSheetView("Sheet1", 1) assert.EqualError(t, err, "view index 1 out of range") _, err = f.GetSheetView("Sheet1", -2) assert.EqualError(t, err, "view index -2 out of range") // Test get sheet view options on not exists worksheet _, err = f.GetSheetView("SheetN", 0) assert.EqualError(t, err, "sheet SheetN does not exist") } ================================================ FILE: slicer.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "sort" "strconv" "strings" "unicode" ) // SlicerOptions represents the settings of the slicer. // // Name specifies the slicer name, should be an existing field name of the given // table or pivot table, this setting is required. // // Cell specifies the left top cell coordinates the position for inserting the // slicer, this setting is required. // // TableSheet specifies the worksheet name of the table or pivot table, this // setting is required. // // TableName specifies the name of the table or pivot table, this setting is // required. // // Caption specifies the caption of the slicer, this setting is optional. // // Macro used for set macro for the slicer, the workbook extension should be // XLSM or XLTM. // // Width specifies the width of the slicer, this setting is optional. // // Height specifies the height of the slicer, this setting is optional. // // DisplayHeader specifies if display header of the slicer, this setting is // optional, the default setting is display. // // ItemDesc specifies descending (Z-A) item sorting, this setting is optional, // and the default setting is false (represents ascending). // // Format specifies the format of the slicer, this setting is optional. type SlicerOptions struct { slicerXML string slicerCacheXML string slicerCacheName string slicerSheetName string slicerSheetRID string drawingXML string Name string Cell string TableSheet string TableName string Caption string Macro string Width uint Height uint DisplayHeader *bool ItemDesc bool Format GraphicOptions } // AddSlicer function inserts a slicer by giving the worksheet name and slicer // settings. // // For example, insert a slicer on the Sheet1!E1 with field Column1 for the // table named Table1: // // err := f.AddSlicer("Sheet1", &excelize.SlicerOptions{ // Name: "Column1", // Cell: "E1", // TableSheet: "Sheet1", // TableName: "Table1", // Caption: "Column1", // Width: 200, // Height: 200, // }) func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error { opts, err := parseSlicerOptions(opts) if err != nil { return err } table, pivotTable, colIdx, err := f.getSlicerSource(opts) if err != nil { return err } extURI, ns := ExtURISlicerListX14, NameSpaceDrawingMLA14 if table != nil { extURI = ExtURISlicerListX15 ns = NameSpaceDrawingMLSlicerX15 } slicerID, err := f.addSheetSlicer(sheet, extURI) if err != nil { return err } slicerCacheName, err := f.setSlicerCache(colIdx, opts, table, pivotTable) if err != nil { return err } slicerName := f.genSlicerName(opts.Name) if err := f.addDrawingSlicer(sheet, slicerName, ns, opts); err != nil { return err } return f.addSlicer(slicerID, xlsxSlicer{ Name: slicerName, Cache: slicerCacheName, Caption: opts.Caption, ShowCaption: opts.DisplayHeader, RowHeight: 251883, }) } // parseSlicerOptions provides a function to parse the format settings of the // slicer with default value. func parseSlicerOptions(opts *SlicerOptions) (*SlicerOptions, error) { if opts == nil { return nil, ErrParameterRequired } if opts.Name == "" || opts.Cell == "" || opts.TableSheet == "" || opts.TableName == "" { return nil, ErrParameterInvalid } if opts.Width == 0 { opts.Width = defaultSlicerWidth } if opts.Height == 0 { opts.Height = defaultSlicerHeight } format := opts.Format graphicOptions, err := format.parseGraphicOptions(nil) if err != nil { return opts, err } opts.Format = *graphicOptions return opts, err } // countSlicers provides a function to get slicer files count storage in the // folder xl/slicers. func (f *File) countSlicers() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicers/slicer") { count++ } return true }) return count } // countSlicerCache provides a function to get slicer cache files count storage // in the folder xl/SlicerCaches. func (f *File) countSlicerCache() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") { count++ } return true }) return count } // getSlicerSource returns the slicer data source table or pivot table settings // and the index of the given slicer fields in the table or pivot table // column. func (f *File) getSlicerSource(opts *SlicerOptions) (*Table, *PivotTableOptions, int, error) { var ( table *Table pivotTable *PivotTableOptions colIdx int err error dataRange string tables []Table pivotTables []PivotTableOptions ) if tables, err = f.GetTables(opts.TableSheet); err != nil { return table, pivotTable, colIdx, err } for _, tbl := range tables { if tbl.Name == opts.TableName { table = &tbl dataRange = fmt.Sprintf("%s!%s", opts.TableSheet, tbl.Range) break } } if table == nil { if pivotTables, err = f.GetPivotTables(opts.TableSheet); err != nil { return table, pivotTable, colIdx, err } for _, tbl := range pivotTables { if tbl.Name == opts.TableName { pivotTable = &tbl dataRange = tbl.DataRange break } } if pivotTable == nil { return table, pivotTable, colIdx, newNoExistTableError(opts.TableName) } } order, _ := f.getTableFieldsOrder(&PivotTableOptions{DataRange: dataRange}) if colIdx = inStrSlice(order, opts.Name, true); colIdx == -1 { return table, pivotTable, colIdx, newInvalidSlicerNameError(opts.Name) } return table, pivotTable, colIdx, err } // addSheetSlicer adds a new slicer and updates the namespace and relationships // parts of the worksheet by giving the worksheet name. func (f *File) addSheetSlicer(sheet, extURI string) (int, error) { var ( slicerID = f.countSlicers() + 1 ws, err = f.workSheetReader(sheet) decodeExtLst = new(decodeExtLst) ) if err != nil { return slicerID, err } if ws.ExtLst != nil { if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return slicerID, err } for _, ext := range decodeExtLst.Ext { if ext.URI == extURI { slicerList := new(decodeSlicerList) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList) for _, slicer := range slicerList.Slicer { if slicer.RID != "" { sheetRelationshipsDrawingXML := f.getSheetRelationshipsTargetByID(sheet, slicer.RID) slicerID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../slicers/slicer"), ".xml")) return slicerID, err } } } } } sheetRelationshipsSlicerXML := "../slicers/slicer" + strconv.Itoa(slicerID) + ".xml" sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipSlicer, sheetRelationshipsSlicerXML, "") f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) return slicerID, f.addSheetTableSlicer(ws, rID, extURI) } // addSheetTableSlicer adds a new table slicer for the worksheet by giving the // worksheet relationships ID and extension URI. func (f *File) addSheetTableSlicer(ws *xlsxWorksheet, rID int, extURI string) error { var ( decodeExtLst = new(decodeExtLst) err error slicerListBytes, extLstBytes []byte ) if ws.ExtLst != nil { if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } } slicerListBytes, _ = xml.Marshal(&xlsxX14SlicerList{ Slicer: []*xlsxX14Slicer{{RID: "rId" + strconv.Itoa(rID)}}, }) ext := &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}}, URI: extURI, Content: string(slicerListBytes), } if extURI == ExtURISlicerListX15 { ext.xmlns = []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}} } decodeExtLst.Ext = append(decodeExtLst.Ext, ext) sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } // addSlicer adds a new slicer to the workbook by giving the slicer ID and // settings. func (f *File) addSlicer(slicerID int, slicer xlsxSlicer) error { slicerXML := "xl/slicers/slicer" + strconv.Itoa(slicerID) + ".xml" slicers, err := f.slicerReader(slicerXML) if err != nil { return err } if err := f.addContentTypePart(slicerID, "slicer"); err != nil { return err } slicers.Slicer = append(slicers.Slicer, slicer) output, err := xml.Marshal(slicers) f.saveFileList(slicerXML, output) return err } // genSlicerName generates a unique slicer cache name by giving the slicer name. func (f *File) genSlicerName(name string) string { var ( cnt int slicerName string names []string ) f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicers/slicer") { slicers, err := f.slicerReader(k.(string)) if err != nil { return true } for _, slicer := range slicers.Slicer { names = append(names, slicer.Name) } } if strings.Contains(k.(string), "xl/timelines/timeline") { timelines, err := f.timelineReader(k.(string)) if err != nil { return true } for _, timeline := range timelines.Timeline { names = append(names, timeline.Name) } } return true }) slicerName = name for { tmp := slicerName if cnt > 0 { tmp = fmt.Sprintf("%s %d", slicerName, cnt) } if inStrSlice(names, tmp, true) == -1 { slicerName = tmp break } cnt++ } return slicerName } // genSlicerCacheName generates a unique slicer cache name by giving the slicer name. func (f *File) genSlicerCacheName(name string) string { var ( cnt int definedNames []string slicerCacheName string ) for _, dn := range f.GetDefinedName() { if dn.Scope == "Workbook" { definedNames = append(definedNames, dn.Name) } } for i, c := range name { if unicode.IsLetter(c) { slicerCacheName += string(c) continue } if i > 0 && (unicode.IsDigit(c) || c == '.') { slicerCacheName += string(c) continue } slicerCacheName += "_" } slicerCacheName = fmt.Sprintf("Slicer_%s", slicerCacheName) for { tmp := slicerCacheName if cnt > 0 { tmp = fmt.Sprintf("%s%d", slicerCacheName, cnt) } if inStrSlice(definedNames, tmp, true) == -1 { slicerCacheName = tmp break } cnt++ } return slicerCacheName } // setSlicerCache check if a slicer cache already exists or add a new slicer // cache by giving the column index, slicer, table options, and returns the // slicer cache name. func (f *File) setSlicerCache(colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) { var ok bool var slicerCacheName string f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") { slicerCache, err := f.slicerCacheReader(k.(string)) if err != nil { return true } if pivotTable != nil && slicerCache.PivotTables != nil { for _, tbl := range slicerCache.PivotTables.PivotTable { if tbl.Name == pivotTable.Name { ok, slicerCacheName = true, slicerCache.Name return false } } } if table == nil || slicerCache.ExtLst == nil { return true } ext := new(xlsxExt) _ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext) if ext.URI == ExtURISlicerCacheDefinition { tableSlicerCache := new(decodeTableSlicerCache) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache) if tableSlicerCache.TableID == table.tID && tableSlicerCache.Column == colIdx+1 { ok, slicerCacheName = true, slicerCache.Name return false } } } return true }) if ok { return slicerCacheName, nil } slicerCacheName = f.genSlicerCacheName(opts.Name) return slicerCacheName, f.addSlicerCache(slicerCacheName, colIdx, opts, table, pivotTable) } // slicerReader provides a function to get the pointer to the structure // after deserialization of xl/slicers/slicer%d.xml. func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) { content, ok := f.Pkg.Load(slicerXML) slicer := &xlsxSlicers{ XMLNSXMC: SourceRelationshipCompatibility.Value, XMLNSX: NameSpaceSpreadSheet.Value, XMLNSXR10: NameSpaceSpreadSheetXR10.Value, } if ok && content != nil { if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(slicer); err != nil && err != io.EOF { return nil, err } } return slicer, nil } // slicerCacheReader provides a function to get the pointer to the structure // after deserialization of xl/slicerCaches/slicerCache%d.xml. func (f *File) slicerCacheReader(slicerCacheXML string) (*xlsxSlicerCacheDefinition, error) { content, ok := f.Pkg.Load(slicerCacheXML) slicerCache := &xlsxSlicerCacheDefinition{} if ok && content != nil { if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(slicerCache); err != nil && err != io.EOF { return nil, err } } return slicerCache, nil } // timelineReader provides a function to get the pointer to the structure // after deserialization of xl/timelines/timeline%d.xml. func (f *File) timelineReader(timelineXML string) (*xlsxTimelines, error) { content, ok := f.Pkg.Load(timelineXML) timeline := &xlsxTimelines{ XMLNSXMC: SourceRelationshipCompatibility.Value, XMLNSX: NameSpaceSpreadSheet.Value, XMLNSXR10: NameSpaceSpreadSheetXR10.Value, } if ok && content != nil { if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(timeline); err != nil && err != io.EOF { return nil, err } } return timeline, nil } // addSlicerCache adds a new slicer cache by giving the slicer cache name, // column index, slicer, and table or pivot table options. func (f *File) addSlicerCache(slicerCacheName string, colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) error { var ( sortOrder string slicerCacheBytes, tableSlicerBytes, extLstBytes []byte extURI = ExtURISlicerCachesX14 slicerCacheID = f.countSlicerCache() + 1 decodeExtLst = new(decodeExtLst) slicerCache = xlsxSlicerCacheDefinition{ XMLNSXMC: SourceRelationshipCompatibility.Value, XMLNSX: NameSpaceSpreadSheet.Value, XMLNSX15: NameSpaceSpreadSheetX15.Value, XMLNSXR10: NameSpaceSpreadSheetXR10.Value, Name: slicerCacheName, SourceName: opts.Name, } ) if opts.ItemDesc { sortOrder = "descending" } if pivotTable != nil { pivotCacheID, err := f.addPivotCacheSlicer(pivotTable) if err != nil { return err } slicerCache.PivotTables = &xlsxSlicerCachePivotTables{ PivotTable: []xlsxSlicerCachePivotTable{ {TabID: f.getSheetID(opts.TableSheet), Name: pivotTable.Name}, }, } slicerCache.Data = &xlsxSlicerCacheData{ Tabular: &xlsxTabularSlicerCache{ PivotCacheID: pivotCacheID, SortOrder: sortOrder, ShowMissing: boolPtr(false), Items: &xlsxTabularSlicerCacheItems{ Count: 1, I: []xlsxTabularSlicerCacheItem{{S: true}}, }, }, } } if table != nil { tableSlicerBytes, _ = xml.Marshal(&xlsxTableSlicerCache{ TableID: table.tID, Column: colIdx + 1, SortOrder: sortOrder, }) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}}, URI: ExtURISlicerCacheDefinition, Content: string(tableSlicerBytes), }) extLstBytes, _ = xml.Marshal(decodeExtLst) slicerCache.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} extURI = ExtURISlicerCachesX15 } slicerCacheXML := "xl/slicerCaches/slicerCache" + strconv.Itoa(slicerCacheID) + ".xml" slicerCacheBytes, _ = xml.Marshal(slicerCache) f.saveFileList(slicerCacheXML, slicerCacheBytes) if err := f.addContentTypePart(slicerCacheID, "slicerCache"); err != nil { return err } if err := f.addWorkbookSlicerCache(slicerCacheID, extURI); err != nil { return err } return f.SetDefinedName(&DefinedName{Name: slicerCacheName, RefersTo: formulaErrorNA}) } // addPivotCacheSlicer adds a new slicer cache by giving the pivot table options // and returns pivot table cache ID. func (f *File) addPivotCacheSlicer(opts *PivotTableOptions) (int, error) { var ( pivotCacheID int pivotCacheBytes, extLstBytes []byte decodeExtLst = new(decodeExtLst) decodeX14PivotCacheDefinition = new(decodeX14PivotCacheDefinition) ) pc, err := f.pivotCacheReader(opts.pivotCacheXML) if err != nil { return pivotCacheID, err } if pc.ExtLst != nil { _ = f.xmlNewDecoder(strings.NewReader("" + pc.ExtLst.Ext + "")).Decode(decodeExtLst) for _, ext := range decodeExtLst.Ext { if ext.URI == ExtURIPivotCacheDefinition { _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeX14PivotCacheDefinition) return decodeX14PivotCacheDefinition.PivotCacheID, err } } } pivotCacheID = f.genPivotCacheDefinitionID() pivotCacheBytes, _ = xml.Marshal(&xlsxX14PivotCacheDefinition{PivotCacheID: pivotCacheID}) ext := &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}}, URI: ExtURIPivotCacheDefinition, Content: string(pivotCacheBytes), } decodeExtLst.Ext = append(decodeExtLst.Ext, ext) extLstBytes, _ = xml.Marshal(decodeExtLst) pc.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} pivotCache, err := xml.Marshal(pc) f.saveFileList(opts.pivotCacheXML, pivotCache) return pivotCacheID, err } // addDrawingSlicer adds a slicer shape and fallback shape by giving the // worksheet name, slicer name, and slicer options. func (f *File) addDrawingSlicer(sheet, slicerName string, ns xml.Attr, opts *SlicerOptions) error { drawingID := f.countDrawings() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" ws, err := f.workSheetReader(sheet) if err != nil { return err } drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) content, cellAnchor, cNvPrID, err := f.cellAnchorShape(sheet, drawingXML, opts.Cell, opts.Width, opts.Height, opts.Format) if err != nil { return err } graphicFrame := xlsxGraphicFrame{ Macro: opts.Macro, NvGraphicFramePr: xlsxNvGraphicFramePr{ CNvPr: &xlsxCNvPr{ ID: cNvPrID, Name: slicerName, Descr: opts.Format.AltText, }, }, Xfrm: xlsxXfrm{Off: xlsxOff{}, Ext: xlsxPositiveSize2D{}}, Graphic: &xlsxGraphic{ GraphicData: &xlsxGraphicData{ URI: NameSpaceDrawingMLSlicer.Value, Sle: &xlsxSle{XMLNS: NameSpaceDrawingMLSlicer.Value, Name: slicerName}, }, }, } graphic, _ := xml.Marshal(graphicFrame) sp := xdrSp{ Macro: opts.Macro, NvSpPr: &xdrNvSpPr{ CNvPr: &xlsxCNvPr{ ID: cNvPrID, Descr: opts.Format.AltText, }, CNvSpPr: &xdrCNvSpPr{ TxBox: true, }, }, SpPr: &xlsxSpPr{ Xfrm: xlsxXfrm{Off: xlsxOff{X: 2914650, Y: 152400}, Ext: xlsxPositiveSize2D{Cx: 1828800, Cy: 2238375}}, SolidFill: &aSolidFill{SrgbClr: &aSrgbClr{Val: stringPtr("FFFFFF")}}, PrstGeom: xlsxPrstGeom{ Prst: "rect", }, Ln: xlsxLineProperties{W: 1, SolidFill: &xlsxInnerXML{Content: ""}}, }, TxBody: &xdrTxBody{ BodyPr: &aBodyPr{VertOverflow: "clip", HorzOverflow: "clip"}, P: []*aP{ {R: &aR{T: "This shape represents a table slicer. Table slicers are not supported in this version of Excel."}}, {R: &aR{T: "If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used."}}, }, }, } shape, _ := xml.Marshal(sp) cellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Format.Locked, FPrintsWithSheet: *opts.Format.PrintObject, } choice := xlsxChoice{Requires: ns.Name.Local, Content: string(graphic)} if ns.Value == NameSpaceDrawingMLA14.Value { // pivot table slicer choice.XMLNSA14 = ns.Value } if ns.Value == NameSpaceDrawingMLSlicerX15.Value { // table slicer choice.XMLNSSle15 = ns.Value } fallback := xlsxFallback{Content: string(shape)} choiceBytes, _ := xml.Marshal(choice) shapeBytes, _ := xml.Marshal(fallback) cellAnchor.AlternateContent = append(cellAnchor.AlternateContent, &xlsxAlternateContent{ XMLNSMC: SourceRelationshipCompatibility.Value, Content: string(choiceBytes) + string(shapeBytes), }) if opts.Format.Positioning == "oneCell" { content.OneCellAnchor = append(content.OneCellAnchor, cellAnchor) } else { content.TwoCellAnchor = append(content.TwoCellAnchor, cellAnchor) } f.Drawings.Store(drawingXML, content) return f.addContentTypePart(drawingID, "drawings") } // addWorkbookSlicerCache add the association ID of the slicer cache in // workbook.xml. func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error { var ( wb *xlsxWorkbook err error idx int appendMode bool decodeExtLst = new(decodeExtLst) decodeSlicerCaches = new(decodeSlicerCaches) x14SlicerCaches = new(xlsxX14SlicerCaches) x15SlicerCaches = new(xlsxX15SlicerCaches) ext *xlsxExt slicerCacheBytes, slicerCachesBytes, extLstBytes []byte ) if wb, err = f.workbookReader(); err != nil { return err } rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipSlicerCache, fmt.Sprintf("/xl/slicerCaches/slicerCache%d.xml", slicerCacheID), "") if wb.ExtLst != nil { // append mode ext if err = f.xmlNewDecoder(strings.NewReader("" + wb.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for idx, ext = range decodeExtLst.Ext { if ext.URI == URI { _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeSlicerCaches) slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)} slicerCacheBytes, _ = xml.Marshal(slicerCache) if URI == ExtURISlicerCachesX14 { // pivot table slicer x14SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes) x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches) } if URI == ExtURISlicerCachesX15 { // table slicer x15SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes) x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches) } decodeExtLst.Ext[idx].Content = string(slicerCachesBytes) appendMode = true } } } if !appendMode { slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)} slicerCacheBytes, _ = xml.Marshal(slicerCache) if URI == ExtURISlicerCachesX14 { x14SlicerCaches.Content = string(slicerCacheBytes) x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}}, URI: ExtURISlicerCachesX14, Content: string(slicerCachesBytes), }) } if URI == ExtURISlicerCachesX15 { x15SlicerCaches.Content = string(slicerCacheBytes) x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}}, URI: ExtURISlicerCachesX15, Content: string(slicerCachesBytes), }) } } sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } // GetSlicers provides the method to get all slicers in a worksheet by a given // worksheet name. Note that, this function does not support getting the height, // width, and graphic options of the slicer shape currently. func (f *File) GetSlicers(sheet string) ([]SlicerOptions, error) { var ( slicers []SlicerOptions ws, err = f.workSheetReader(sheet) decodeExtLst = new(decodeExtLst) ) if err != nil { return slicers, err } if ws.ExtLst == nil { return slicers, err } target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/") if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return slicers, err } for _, ext := range decodeExtLst.Ext { if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 { slicerList := new(decodeSlicerList) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(&slicerList) for _, slicer := range slicerList.Slicer { if slicer.RID != "" { opts, err := f.getSlicers(sheet, slicer.RID, drawingXML) if err != nil { return slicers, err } slicers = append(slicers, opts...) } } } } return slicers, err } // getSlicerCache provides a function to get a slicer cache by given slicer // cache name and slicer options. func (f *File) getSlicerCache(slicerCacheName string, opt *SlicerOptions) *xlsxSlicerCacheDefinition { var ( err error slicerCache *xlsxSlicerCacheDefinition ) f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") { slicerCache, err = f.slicerCacheReader(k.(string)) if err != nil { return true } if slicerCache.Name == slicerCacheName { opt.slicerCacheXML = k.(string) return false } } return true }) return slicerCache } // getSlicers provides a function to get slicers options by given worksheet // name, slicer part relationship ID and drawing part path. func (f *File) getSlicers(sheet, rID, drawingXML string) ([]SlicerOptions, error) { var ( opts []SlicerOptions sheetRelationshipsSlicerXML = f.getSheetRelationshipsTargetByID(sheet, rID) slicerXML = strings.ReplaceAll(sheetRelationshipsSlicerXML, "..", "xl") slicers, err = f.slicerReader(slicerXML) ) if err != nil { return opts, err } for _, slicer := range slicers.Slicer { opt := SlicerOptions{ slicerXML: slicerXML, slicerCacheName: slicer.Cache, slicerSheetName: sheet, slicerSheetRID: rID, drawingXML: drawingXML, Name: slicer.Name, Caption: slicer.Caption, DisplayHeader: slicer.ShowCaption, } slicerCache := f.getSlicerCache(slicer.Cache, &opt) if slicerCache == nil { return opts, err } if err := f.extractTableSlicer(slicerCache, &opt); err != nil { return opts, err } if err := f.extractPivotTableSlicer(slicerCache, &opt); err != nil { return opts, err } if err = f.extractSlicerCellAnchor(drawingXML, &opt); err != nil { return opts, err } opts = append(opts, opt) } return opts, err } // extractTableSlicer extract table slicer options from slicer cache. func (f *File) extractTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error { if slicerCache.ExtLst != nil { tables, err := f.getTables() if err != nil { return err } ext := new(xlsxExt) _ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext) if ext.URI == ExtURISlicerCacheDefinition { tableSlicerCache := new(decodeTableSlicerCache) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache) opt.ItemDesc = tableSlicerCache.SortOrder == "descending" for sheetName, sheetTables := range tables { for _, table := range sheetTables { if tableSlicerCache.TableID == table.tID { opt.TableName = table.Name opt.TableSheet = sheetName } } } } } return nil } // extractPivotTableSlicer extract pivot table slicer options from slicer cache. func (f *File) extractPivotTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error { pivotTables, err := f.getPivotTables() if err != nil { return err } if slicerCache.PivotTables != nil { for _, pt := range slicerCache.PivotTables.PivotTable { opt.TableName = pt.Name for sheetName, sheetPivotTables := range pivotTables { for _, pivotTable := range sheetPivotTables { if opt.TableName == pivotTable.Name { opt.TableSheet = sheetName } } } } if slicerCache.Data != nil && slicerCache.Data.Tabular != nil { opt.ItemDesc = slicerCache.Data.Tabular.SortOrder == "descending" } } return err } // extractSlicerCellAnchor extract slicer drawing object from two cell anchor by // giving drawing part path and slicer options. func (f *File) extractSlicerCellAnchor(drawingXML string, opt *SlicerOptions) error { var ( wsDr *xlsxWsDr err error ) if wsDr, _, err = f.drawingParser(drawingXML); err != nil { return err } wsDr.mu.Lock() defer wsDr.mu.Unlock() for _, anchor := range wsDr.OneCellAnchor { if err = f.extractSlicerFromAnchor(anchor, opt); err != nil { return err } if err = f.extractSlicerFromDecodeAnchor(anchor, opt); err != nil { return err } } for _, anchor := range wsDr.TwoCellAnchor { if err = f.extractSlicerFromAnchor(anchor, opt); err != nil { return err } if err = f.extractSlicerFromDecodeAnchor(anchor, opt); err != nil { return err } } return err } // extractSlicerFromAnchor extract slicer drawing object from cell anchor by // giving drawing part path and slicer options. func (f *File) extractSlicerFromAnchor(anchor *xdrCellAnchor, opt *SlicerOptions) error { var ( deChoice = new(decodeChoice) err error ) for _, ac := range anchor.AlternateContent { if ac != nil { _ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice) if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value { if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name { if anchor.From != nil { opt.Macro = deChoice.GraphicFrame.Macro if opt.Cell, err = CoordinatesToCellName(anchor.From.Col+1, anchor.From.Row+1); err != nil { return err } } return err } } } } return err } // extractSlicerFromDecodeAnchor extract slicer drawing object from decode cell // anchor by giving drawing part path and slicer options. func (f *File) extractSlicerFromDecodeAnchor(anchor *xdrCellAnchor, opt *SlicerOptions) error { var ( deCellAnchor = new(decodeCellAnchor) deChoice = new(decodeChoice) err error ) _ = f.xmlNewDecoder(strings.NewReader("" + anchor.GraphicFrame + "")).Decode(&deCellAnchor) for _, ac := range deCellAnchor.AlternateContent { if ac != nil { _ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice) if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value { if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name { if deCellAnchor.From != nil { opt.Macro = deChoice.GraphicFrame.Macro if opt.Cell, err = CoordinatesToCellName(deCellAnchor.From.Col+1, deCellAnchor.From.Row+1); err != nil { return err } } return err } } } } return err } // getAllSlicers provides a function to get all slicers in a workbook. func (f *File) getAllSlicers() (map[string][]SlicerOptions, error) { slicers := map[string][]SlicerOptions{} for _, sheetName := range f.GetSheetList() { sles, err := f.GetSlicers(sheetName) e := ErrSheetNotExist{sheetName} if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() { return slicers, err } slicers[sheetName] = append(slicers[sheetName], sles...) } return slicers, nil } // DeleteSlicer provides the method to delete a slicer by a given slicer name. func (f *File) DeleteSlicer(name string) error { sles, err := f.getAllSlicers() if err != nil { return err } for _, slicers := range sles { for _, slicer := range slicers { if slicer.Name != name { continue } _ = f.deleteSlicer(slicer) return f.deleteSlicerCache(sles, slicer) } } return newNoExistSlicerError(name) } // getSlicers provides a function to delete slicer by given slicer options. func (f *File) deleteSlicer(opts SlicerOptions) error { slicers, err := f.slicerReader(opts.slicerXML) if err != nil { return err } for i := 0; i < len(slicers.Slicer); i++ { if slicers.Slicer[i].Name == opts.Name { slicers.Slicer = append(slicers.Slicer[:i], slicers.Slicer[i+1:]...) i-- } } if len(slicers.Slicer) == 0 { var ( extLstBytes []byte ws, err = f.workSheetReader(opts.slicerSheetName) decodeExtLst = new(decodeExtLst) ) if err != nil { return err } if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for i, ext := range decodeExtLst.Ext { if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 { slicerList := new(decodeSlicerList) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList) for _, slicer := range slicerList.Slicer { if slicer.RID == opts.slicerSheetRID { decodeExtLst.Ext = append(decodeExtLst.Ext[:i], decodeExtLst.Ext[i+1:]...) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} f.Pkg.Delete(opts.slicerXML) _ = f.removeContentTypesPart(ContentTypeSlicer, "/"+opts.slicerXML) f.deleteSheetRelationships(opts.slicerSheetName, opts.slicerSheetRID) return err } } } } } output, err := xml.Marshal(slicers) f.saveFileList(opts.slicerXML, output) return err } // deleteSlicerCache provides a function to delete the slicer cache by giving // slicer options if the slicer cache is no longer used. func (f *File) deleteSlicerCache(sles map[string][]SlicerOptions, opts SlicerOptions) error { for _, slicers := range sles { for _, slicer := range slicers { if slicer.Name != opts.Name && slicer.slicerCacheName == opts.slicerCacheName { return nil } } } if err := f.DeleteDefinedName(&DefinedName{Name: opts.slicerCacheName}); err != nil { return err } f.Pkg.Delete(opts.slicerCacheXML) return f.removeContentTypesPart(ContentTypeSlicerCache, "/"+opts.slicerCacheXML) } ================================================ FILE: slicer_test.go ================================================ package excelize import ( "fmt" "math/rand" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestSlicer(t *testing.T) { f := NewFile() disable, colName := false, "_!@#$%^&*()-+=|\\/<>" assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName)) // Create table in a worksheet assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "I1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: colName, Cell: "M1", TableSheet: "Sheet1", TableName: "Table1", Caption: colName, Macro: "Button1_Click", Width: 200, Height: 200, DisplayHeader: &disable, ItemDesc: true, Format: GraphicOptions{Positioning: "oneCell"}, })) // Test get table slicers slicers, err := f.GetSlicers("Sheet1") assert.NoError(t, err) assert.Equal(t, "Column1", slicers[0].Name) assert.Equal(t, "E1", slicers[0].Cell) assert.Equal(t, "Sheet1", slicers[0].TableSheet) assert.Equal(t, "Table1", slicers[0].TableName) assert.Equal(t, "Column1", slicers[0].Caption) assert.Equal(t, "Column1 1", slicers[1].Name) assert.Equal(t, "I1", slicers[1].Cell) assert.Equal(t, "Sheet1", slicers[1].TableSheet) assert.Equal(t, "Table1", slicers[1].TableName) assert.Equal(t, "Column1", slicers[1].Caption) assert.Equal(t, colName, slicers[2].Name) assert.Equal(t, "M1", slicers[2].Cell) assert.Equal(t, "Sheet1", slicers[2].TableSheet) assert.Equal(t, "Table1", slicers[2].TableName) assert.Equal(t, colName, slicers[2].Caption) assert.Equal(t, "Button1_Click", slicers[2].Macro) assert.False(t, *slicers[2].DisplayHeader) assert.True(t, slicers[2].ItemDesc) // Test create two pivot tables in a new worksheet _, err = f.NewSheet("Sheet2") assert.NoError(t, err) // Create some data in a sheet month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} year := []int{2017, 2018, 2019} types := []string{"Meat", "Dairy", "Beverages", "Produce"} region := []string{"East", "West", "North", "South"} assert.NoError(t, f.SetSheetRow("Sheet2", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})) for row := 2; row < 32; row++ { assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("A%d", row), month[rand.Intn(12)])) assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("B%d", row), year[rand.Intn(3)])) assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("C%d", row), types[rand.Intn(4)])) assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("D%d", row), rand.Intn(5000))) assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("E%d", row), region[rand.Intn(4)])) } assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet2!A1:E31", PivotTableRange: "Sheet2!G2:M34", Name: "PivotTable1", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Filter: []PivotTableField{{Data: "Region"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, ShowError: true, PivotTableStyleName: "PivotStyleLight16", })) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet2!A1:E31", PivotTableRange: "Sheet2!U34:O2", Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}}, Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}}, RowGrandTotals: true, ColGrandTotals: true, ShowDrill: true, ShowRowHeaders: true, ShowColHeaders: true, ShowLastColumn: true, })) // Test add a pivot table slicer assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Month", Cell: "G42", TableSheet: "Sheet2", TableName: "PivotTable1", Caption: "Month", })) // Test add a pivot table slicer with duplicate field name assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Month", Cell: "K42", TableSheet: "Sheet2", TableName: "PivotTable1", Caption: "Month", })) // Test add a pivot table slicer for another pivot table in a worksheet assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Region", Cell: "O42", TableSheet: "Sheet2", TableName: "PivotTable2", Caption: "Region", ItemDesc: true, })) // Test get pivot table slicers slicers, err = f.GetSlicers("Sheet2") assert.NoError(t, err) assert.Equal(t, "Month", slicers[0].Name) assert.Equal(t, "G42", slicers[0].Cell) assert.Equal(t, "Sheet2", slicers[0].TableSheet) assert.Equal(t, "PivotTable1", slicers[0].TableName) assert.Equal(t, "Month", slicers[0].Caption) assert.Equal(t, "Month 1", slicers[1].Name) assert.Equal(t, "K42", slicers[1].Cell) assert.Equal(t, "Sheet2", slicers[1].TableSheet) assert.Equal(t, "PivotTable1", slicers[1].TableName) assert.Equal(t, "Month", slicers[1].Caption) assert.Equal(t, "Region", slicers[2].Name) assert.Equal(t, "O42", slicers[2].Cell) assert.Equal(t, "Sheet2", slicers[2].TableSheet) assert.Equal(t, "PivotTable2", slicers[2].TableName) assert.Equal(t, "Region", slicers[2].Caption) assert.True(t, slicers[2].ItemDesc) // Test add a table slicer with empty slicer options assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil)) // Test add a table slicer with invalid slicer options for _, opts := range []*SlicerOptions{ {Cell: "Q1", TableSheet: "Sheet1", TableName: "Table1"}, {Name: "Column", Cell: "Q1", TableSheet: "Sheet1"}, {Name: "Column", TableSheet: "Sheet1", TableName: "Table1"}, } { assert.Equal(t, ErrParameterInvalid, f.AddSlicer("Sheet1", opts)) } // Test add a table slicer with not exist worksheet assert.EqualError(t, f.AddSlicer("SheetN", &SlicerOptions{ Name: "Column2", Cell: "Q1", TableSheet: "SheetN", TableName: "Table1", }), "sheet SheetN does not exist") // Test add slicer with invalid positioning types assert.Equal(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column2", Cell: "Q1", TableSheet: "Sheet1", TableName: "Table1", Format: GraphicOptions{Positioning: "x"}, }), newInvalidOptionalValue("Positioning", "x", supportedPositioning)) // Test add a table slicer with not exist table name assert.Equal(t, newNoExistTableError("Table2"), f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column2", Cell: "Q1", TableSheet: "Sheet1", TableName: "Table2", })) // Test add a table slicer with invalid slicer name assert.Equal(t, newInvalidSlicerNameError("Column6"), f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column6", Cell: "Q1", TableSheet: "Sheet1", TableName: "Table1", })) workbookPath := filepath.Join("test", "TestAddSlicer.xlsm") file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin")) assert.NoError(t, err) assert.NoError(t, f.AddVBAProject(file)) assert.NoError(t, f.SaveAs(workbookPath)) assert.NoError(t, f.Close()) // Test add a pivot table slicer with unsupported charset pivot table f, err = OpenFile(workbookPath) assert.NoError(t, err) f.Pkg.Store("xl/pivotTables/pivotTable2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Month", Cell: "G42", TableSheet: "Sheet2", TableName: "PivotTable1", Caption: "Month", }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test open a workbook and get already exist slicers f, err = OpenFile(workbookPath) assert.NoError(t, err) slicers, err = f.GetSlicers("Sheet1") assert.NoError(t, err) assert.Equal(t, "Column1", slicers[0].Name) assert.Equal(t, "E1", slicers[0].Cell) assert.Equal(t, "Sheet1", slicers[0].TableSheet) assert.Equal(t, "Table1", slicers[0].TableName) assert.Equal(t, "Column1", slicers[0].Caption) assert.Equal(t, "Column1 1", slicers[1].Name) assert.Equal(t, "I1", slicers[1].Cell) assert.Equal(t, "Sheet1", slicers[1].TableSheet) assert.Equal(t, "Table1", slicers[1].TableName) assert.Equal(t, "Column1", slicers[1].Caption) assert.Equal(t, colName, slicers[2].Name) assert.Equal(t, "M1", slicers[2].Cell) assert.Equal(t, "Sheet1", slicers[2].TableSheet) assert.Equal(t, "Table1", slicers[2].TableName) assert.Equal(t, colName, slicers[2].Caption) assert.Equal(t, "Button1_Click", slicers[2].Macro) assert.False(t, *slicers[2].DisplayHeader) assert.True(t, slicers[2].ItemDesc) slicers, err = f.GetSlicers("Sheet2") assert.NoError(t, err) assert.Equal(t, "Month", slicers[0].Name) assert.Equal(t, "G42", slicers[0].Cell) assert.Equal(t, "Sheet2", slicers[0].TableSheet) assert.Equal(t, "PivotTable1", slicers[0].TableName) assert.Equal(t, "Month", slicers[0].Caption) assert.Equal(t, "Month 1", slicers[1].Name) assert.Equal(t, "K42", slicers[1].Cell) assert.Equal(t, "Sheet2", slicers[1].TableSheet) assert.Equal(t, "PivotTable1", slicers[1].TableName) assert.Equal(t, "Month", slicers[1].Caption) assert.Equal(t, "Region", slicers[2].Name) assert.Equal(t, "O42", slicers[2].Cell) assert.Equal(t, "Sheet2", slicers[2].TableSheet) assert.Equal(t, "PivotTable2", slicers[2].TableName) assert.Equal(t, "Region", slicers[2].Caption) assert.True(t, slicers[2].ItemDesc) // Test add a pivot table slicer with workbook which contains timeline f, err = OpenFile(workbookPath) assert.NoError(t, err) f.Pkg.Store("xl/timelines/timeline1.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX15.Value))) assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Month", Cell: "G42", TableSheet: "Sheet2", TableName: "PivotTable1", Caption: "Month", })) assert.NoError(t, f.Close()) // Test add a pivot table slicer with unsupported charset timeline f, err = OpenFile(workbookPath) assert.NoError(t, err) f.Pkg.Store("xl/timelines/timeline1.xml", MacintoshCyrillicCharset) assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{ Name: "Month", Cell: "G42", TableSheet: "Sheet2", TableName: "PivotTable1", Caption: "Month", })) assert.NoError(t, f.Close()) // Test add a table slicer with invalid worksheet extension list f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: "<>"} assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", })) assert.NoError(t, f.Close()) // Test add a table slicer with unsupported charset slicer f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) f.Pkg.Store("xl/slicers/slicer2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableName: "Table1", TableSheet: "Sheet1", }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test add a table slicer with read workbook error f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) f.WorkBook.ExtLst = &xlsxExtLst{Ext: "<>"} assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableName: "Table1", TableSheet: "Sheet1", })) assert.NoError(t, f.Close()) // Test add a table slicer with unsupported charset content types f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableName: "Table1", TableSheet: "Sheet1", }), "XML syntax error on line 1: invalid UTF-8") f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.addSlicer(0, xlsxSlicer{}), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) f = NewFile() // Create table in a worksheet assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) f.Pkg.Store("xl/drawings/drawing2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) f = NewFile() // Test get sheet slicers without slicer slicers, err = f.GetSlicers("Sheet1") assert.NoError(t, err) assert.Empty(t, slicers) // Test get sheet slicers with not exist worksheet name _, err = f.GetSlicers("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) f, err = OpenFile(workbookPath) assert.NoError(t, err) // Test get sheet slicers with unsupported charset slicer cache f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset) _, err = f.GetSlicers("Sheet1") assert.NoError(t, err) // Test get sheet slicers with unsupported charset slicer f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset) _, err = f.GetSlicers("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get sheet slicers with invalid worksheet extension list ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst.Ext = "<>" _, err = f.GetSlicers("Sheet1") assert.Error(t, err) assert.NoError(t, f.Close()) f, err = OpenFile(workbookPath) assert.NoError(t, err) // Test get sheet slicers without slicer cache f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") { f.Pkg.Delete(k.(string)) } return true }) slicers, err = f.GetSlicers("Sheet1") assert.NoError(t, err) assert.Empty(t, slicers) assert.NoError(t, f.Close()) // Test open a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor f, err = OpenFile(workbookPath) assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`
-1-1`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value))) _, err = f.GetSlicers("Sheet1") assert.Equal(t, newCoordinatesToCellNameError(0, 0), err) assert.NoError(t, f.Close()) // Test open a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor f, err = OpenFile(workbookPath) assert.NoError(t, err) // Test open a workbook and get sheet slicer with invalid cell reference in the drawing two cell anchor f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`-1-1`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value))) _, err = f.GetSlicers("Sheet1") assert.Equal(t, newCoordinatesToCellNameError(0, 0), err) // Test get sheet slicer without slicer shape in the drawing part f.Drawings.Delete("xl/drawings/drawing1.xml") f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(``, NameSpaceDrawingMLSpreadSheet.Value))) _, err = f.GetSlicers("Sheet1") assert.NoError(t, err) f.Drawings.Delete("xl/drawings/drawing1.xml") // Test get sheet slicers with unsupported charset drawing part f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset) _, err = f.GetSlicers("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get sheet slicers with unsupported charset table f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) _, err = f.GetSlicers("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get sheet slicers with unsupported charset pivot table f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset) _, err = f.GetSlicers("Sheet2") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test create a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", Format: GraphicOptions{Positioning: "oneCell"}, })) drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml") assert.True(t, ok) drawing.(*xlsxWsDr).OneCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1} _, err = f.GetSlicers("Sheet1") assert.Equal(t, newCoordinatesToCellNameError(0, 0), err) assert.NoError(t, f.Close()) // Test create a workbook and get sheet slicer with invalid cell reference in the drawing two cell anchor f = NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", })) drawing, ok = f.Drawings.Load("xl/drawings/drawing1.xml") assert.True(t, ok) drawing.(*xlsxWsDr).TwoCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1} _, err = f.GetSlicers("Sheet1") assert.Equal(t, newCoordinatesToCellNameError(0, 0), err) assert.NoError(t, f.Close()) // Test open a workbook and delete slicers f, err = OpenFile(workbookPath) assert.NoError(t, err) for _, name := range []string{colName, "Column1 1", "Column1"} { assert.NoError(t, f.DeleteSlicer(name)) } for _, name := range []string{"Month", "Month 1", "Region"} { assert.NoError(t, f.DeleteSlicer(name)) } // Test delete slicer with no exits slicer name assert.Equal(t, newNoExistSlicerError("x"), f.DeleteSlicer("x")) assert.NoError(t, f.Close()) // Test open a workbook and delete sheet slicer with unsupported charset slicer cache f, err = OpenFile(workbookPath) assert.NoError(t, err) f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.DeleteSlicer("Column1"), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestAddSheetSlicer(t *testing.T) { f := NewFile() // Test add sheet slicer with not exist worksheet name _, err := f.addSheetSlicer("SheetN", ExtURISlicerListX15) assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) } func TestAddSheetTableSlicer(t *testing.T) { f := NewFile() // Test add sheet table slicer with invalid worksheet extension assert.Error(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: "<>"}}, 0, ExtURISlicerListX15)) // Test add sheet table slicer with existing worksheet extension assert.NoError(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: fmt.Sprintf("", ExtURITimelineRefs)}}, 1, ExtURISlicerListX15)) assert.NoError(t, f.Close()) } func TestSetSlicerCache(t *testing.T) { f := NewFile() f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset) _, err := f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil) assert.NoError(t, err) assert.NoError(t, f.Close()) f = NewFile() f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil) assert.NoError(t, err) assert.NoError(t, f.Close()) f = NewFile() f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil) assert.NoError(t, err) assert.NoError(t, f.Close()) f = NewFile() f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil) assert.NoError(t, err) assert.NoError(t, f.Close()) f = NewFile() f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value))) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil) assert.NoError(t, err) assert.NoError(t, f.Close()) } func TestDeleteSlicer(t *testing.T) { f, slicerXML := NewFile(), "xl/slicers/slicer1.xml" assert.NoError(t, f.AddTable("Sheet1", &Table{ Name: "Table1", Range: "A1:D5", })) assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ Name: "Column1", Cell: "E1", TableSheet: "Sheet1", TableName: "Table1", Caption: "Column1", })) // Test delete sheet slicers with invalid worksheet extension list ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst.Ext = "<>" assert.Error(t, f.deleteSlicer(SlicerOptions{ slicerXML: slicerXML, slicerSheetName: "Sheet1", Name: "Column1", })) // Test delete slicer with unsupported charset worksheet f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.deleteSlicer(SlicerOptions{ slicerXML: slicerXML, slicerSheetName: "Sheet1", Name: "Column1", }), "XML syntax error on line 1: invalid UTF-8") // Test delete slicer with unsupported charset slicer f.Pkg.Store(slicerXML, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteSlicer(SlicerOptions{slicerXML: slicerXML}), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestDeleteSlicerCache(t *testing.T) { f := NewFile() // Test delete slicer cache with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteSlicerCache(nil, SlicerOptions{}), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestAddSlicerCache(t *testing.T) { f := NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.addSlicerCache("Slicer1", 0, &SlicerOptions{}, &Table{}, nil), "XML syntax error on line 1: invalid UTF-8") // Test add a pivot table cache slicer with unsupported charset pivotCacheXML := "xl/pivotCache/pivotCacheDefinition1.xml" f.Pkg.Store(pivotCacheXML, MacintoshCyrillicCharset) assert.EqualError(t, f.addSlicerCache("Slicer1", 0, &SlicerOptions{}, nil, &PivotTableOptions{pivotCacheXML: pivotCacheXML}), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestAddDrawingSlicer(t *testing.T) { f := NewFile() // Test add a drawing slicer with not exist worksheet assert.EqualError(t, f.addDrawingSlicer("SheetN", "Column2", NameSpaceDrawingMLSlicerX15, &SlicerOptions{ Name: "Column2", Cell: "Q1", TableSheet: "SheetN", TableName: "Table1", }), "sheet SheetN does not exist") // Test add a drawing slicer with invalid cell reference assert.EqualError(t, f.addDrawingSlicer("Sheet1", "Column2", NameSpaceDrawingMLSlicerX15, &SlicerOptions{ Name: "Column2", Cell: "A", TableSheet: "Sheet1", TableName: "Table1", }), "cannot convert cell \"A\" to coordinates: invalid cell name \"A\"") assert.NoError(t, f.Close()) } func TestAddWorkbookSlicerCache(t *testing.T) { // Test add a workbook slicer cache with unsupported charset workbook f := NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.addWorkbookSlicerCache(1, ExtURISlicerCachesX15), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } func TestGenSlicerCacheName(t *testing.T) { f := NewFile() assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "Slicer_Column_1", RefersTo: formulaErrorNA})) assert.Equal(t, "Slicer_Column_11", f.genSlicerCacheName("Column 1")) assert.NoError(t, f.Close()) } func TestAddPivotCacheSlicer(t *testing.T) { f := NewFile() pivotCacheXML := "xl/pivotCache/pivotCacheDefinition1.xml" // Test add a pivot table cache slicer with existing extension list f.Pkg.Store(pivotCacheXML, []byte(fmt.Sprintf(``, NameSpaceSpreadSheet.Value, ExtURIPivotCacheDefinition))) _, err := f.addPivotCacheSlicer(&PivotTableOptions{ pivotCacheXML: pivotCacheXML, }) assert.NoError(t, err) } ================================================ FILE: sparkline.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "io" "sort" "strings" ) // getSparklineGroupPresets returns the preset list of sparkline group to create // x14:sparklineGroups element. func getSparklineGroupPresets() []*xlsxX14SparklineGroup { return []*xlsxX14SparklineGroup{ { ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(5)}, ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(4)}, ColorLow: &xlsxColor{Theme: intPtr(4)}, }, // 0 { ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(5)}, ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(4)}, ColorLow: &xlsxColor{Theme: intPtr(4)}, }, // 1 { ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(6)}, ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(5)}, ColorLow: &xlsxColor{Theme: intPtr(5)}, }, // 2 { ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(7)}, ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(6)}, ColorLow: &xlsxColor{Theme: intPtr(6)}, }, // 3 { ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(8)}, ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(7)}, ColorLow: &xlsxColor{Theme: intPtr(7)}, }, // 4 { ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(9)}, ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(8)}, ColorLow: &xlsxColor{Theme: intPtr(8)}, }, // 5 { ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(4)}, ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262}, ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921}, ColorLast: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921}, ColorHigh: &xlsxColor{Theme: intPtr(9)}, ColorLow: &xlsxColor{Theme: intPtr(9)}, }, // 6 { ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(5)}, ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(5)}, ColorLow: &xlsxColor{Theme: intPtr(5)}, }, // 7 { ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(6)}, ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, }, // 8 { ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(7)}, ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, }, // 9 { ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(8)}, ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, }, // 10 { ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(9)}, ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, }, // 11 { ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorNegative: &xlsxColor{Theme: intPtr(4)}, ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, }, // 12 { ColorSeries: &xlsxColor{Theme: intPtr(4)}, ColorNegative: &xlsxColor{Theme: intPtr(5)}, ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, }, // 13 { ColorSeries: &xlsxColor{Theme: intPtr(5)}, ColorNegative: &xlsxColor{Theme: intPtr(6)}, ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, }, // 14 { ColorSeries: &xlsxColor{Theme: intPtr(6)}, ColorNegative: &xlsxColor{Theme: intPtr(7)}, ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, }, // 15 { ColorSeries: &xlsxColor{Theme: intPtr(7)}, ColorNegative: &xlsxColor{Theme: intPtr(8)}, ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, }, // 16 { ColorSeries: &xlsxColor{Theme: intPtr(8)}, ColorNegative: &xlsxColor{Theme: intPtr(9)}, ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, }, // 17 { ColorSeries: &xlsxColor{Theme: intPtr(9)}, ColorNegative: &xlsxColor{Theme: intPtr(4)}, ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, }, // 18 { ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262}, }, // 19 { ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262}, }, // 20 { ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262}, }, // 21 { ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262}, }, // 22 { ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262}, }, // 23 { ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262}, ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: 0.79998168889431442}, ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262}, ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262}, }, // 24 { ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.499984740745262}, ColorNegative: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, ColorMarkers: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893}, }, // 25 { ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.34998626667073579}, ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, ColorMarkers: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, ColorFirst: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, ColorLast: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, ColorHigh: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, ColorLow: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893}, }, // 26 { ColorSeries: &xlsxColor{RGB: "FF323232"}, ColorNegative: &xlsxColor{RGB: "FFD00000"}, ColorMarkers: &xlsxColor{RGB: "FFD00000"}, ColorFirst: &xlsxColor{RGB: "FFD00000"}, ColorLast: &xlsxColor{RGB: "FFD00000"}, ColorHigh: &xlsxColor{RGB: "FFD00000"}, ColorLow: &xlsxColor{RGB: "FFD00000"}, }, // 27 { ColorSeries: &xlsxColor{RGB: "FF000000"}, ColorNegative: &xlsxColor{RGB: "FF0070C0"}, ColorMarkers: &xlsxColor{RGB: "FF0070C0"}, ColorFirst: &xlsxColor{RGB: "FF0070C0"}, ColorLast: &xlsxColor{RGB: "FF0070C0"}, ColorHigh: &xlsxColor{RGB: "FF0070C0"}, ColorLow: &xlsxColor{RGB: "FF0070C0"}, }, // 28 { ColorSeries: &xlsxColor{RGB: "FF376092"}, ColorNegative: &xlsxColor{RGB: "FFD00000"}, ColorMarkers: &xlsxColor{RGB: "FFD00000"}, ColorFirst: &xlsxColor{RGB: "FFD00000"}, ColorLast: &xlsxColor{RGB: "FFD00000"}, ColorHigh: &xlsxColor{RGB: "FFD00000"}, ColorLow: &xlsxColor{RGB: "FFD00000"}, }, // 29 { ColorSeries: &xlsxColor{RGB: "FF0070C0"}, ColorNegative: &xlsxColor{RGB: "FF000000"}, ColorMarkers: &xlsxColor{RGB: "FF000000"}, ColorFirst: &xlsxColor{RGB: "FF000000"}, ColorLast: &xlsxColor{RGB: "FF000000"}, ColorHigh: &xlsxColor{RGB: "FF000000"}, ColorLow: &xlsxColor{RGB: "FF000000"}, }, // 30 { ColorSeries: &xlsxColor{RGB: "FF5F5F5F"}, ColorNegative: &xlsxColor{RGB: "FFFFB620"}, ColorMarkers: &xlsxColor{RGB: "FFD70077"}, ColorFirst: &xlsxColor{RGB: "FF5687C2"}, ColorLast: &xlsxColor{RGB: "FF359CEB"}, ColorHigh: &xlsxColor{RGB: "FF56BE79"}, ColorLow: &xlsxColor{RGB: "FFFF5055"}, }, // 31 { ColorSeries: &xlsxColor{RGB: "FF5687C2"}, ColorNegative: &xlsxColor{RGB: "FFFFB620"}, ColorMarkers: &xlsxColor{RGB: "FFD70077"}, ColorFirst: &xlsxColor{RGB: "FF777777"}, ColorLast: &xlsxColor{RGB: "FF359CEB"}, ColorHigh: &xlsxColor{RGB: "FF56BE79"}, ColorLow: &xlsxColor{RGB: "FFFF5055"}, }, // 32 { ColorSeries: &xlsxColor{RGB: "FFC6EFCE"}, ColorNegative: &xlsxColor{RGB: "FFFFC7CE"}, ColorMarkers: &xlsxColor{RGB: "FF8CADD6"}, ColorFirst: &xlsxColor{RGB: "FFFFDC47"}, ColorLast: &xlsxColor{RGB: "FFFFEB9C"}, ColorHigh: &xlsxColor{RGB: "FF60D276"}, ColorLow: &xlsxColor{RGB: "FFFF5367"}, }, // 33 { ColorSeries: &xlsxColor{RGB: "FF00B050"}, ColorNegative: &xlsxColor{RGB: "FFFF0000"}, ColorMarkers: &xlsxColor{RGB: "FF0070C0"}, ColorFirst: &xlsxColor{RGB: "FFFFC000"}, ColorLast: &xlsxColor{RGB: "FFFFC000"}, ColorHigh: &xlsxColor{RGB: "FF00B050"}, ColorLow: &xlsxColor{RGB: "FFFF0000"}, }, // 34 { ColorSeries: &xlsxColor{Theme: intPtr(3)}, ColorNegative: &xlsxColor{Theme: intPtr(9)}, ColorMarkers: &xlsxColor{Theme: intPtr(8)}, ColorFirst: &xlsxColor{Theme: intPtr(4)}, ColorLast: &xlsxColor{Theme: intPtr(5)}, ColorHigh: &xlsxColor{Theme: intPtr(6)}, ColorLow: &xlsxColor{Theme: intPtr(7)}, }, // 35 { ColorSeries: &xlsxColor{Theme: intPtr(1)}, ColorNegative: &xlsxColor{Theme: intPtr(9)}, ColorMarkers: &xlsxColor{Theme: intPtr(8)}, ColorFirst: &xlsxColor{Theme: intPtr(4)}, ColorLast: &xlsxColor{Theme: intPtr(5)}, ColorHigh: &xlsxColor{Theme: intPtr(6)}, ColorLow: &xlsxColor{Theme: intPtr(7)}, }, // 36 } } // AddSparkline provides a function to add sparklines to the worksheet by // given formatting options. Sparklines are small charts that fit in a single // cell and are used to show trends in data. Sparklines are a feature of Excel // 2010 and later only. You can write them to workbook that can be read by Excel // 2007, but they won't be displayed. For example, add a grouped sparkline. // Changes are applied to all three: // // err := f.AddSparkline("Sheet1", &excelize.SparklineOptions{ // Location: []string{"A1", "A2", "A3"}, // Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"}, // Markers: true, // }) // // The following shows the formatting options of sparkline supported by excelize: // // Parameter | Description // -------------+-------------------------------------------- // Location | Required, must have the same number with 'Range' parameter // Range | Required, must have the same number with 'Location' parameter // Type | Enumeration value: line, column, win_loss // Style | Value range: 0 - 35 // Hight | Toggle sparkline high points // Low | Toggle sparkline low points // First | Toggle sparkline first points // Last | Toggle sparkline last points // Negative | Toggle sparkline negative points // Markers | Toggle sparkline markers // Axis | Used to specify if show horizontal axis // Reverse | Used to specify if enable plot data right-to-left // SeriesColor | An RGB Color is specified as RRGGBB func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error { var ( err error ws *xlsxWorksheet sparkType string sparkTypes map[string]string specifiedSparkTypes string ok bool group *xlsxX14SparklineGroup groups *xlsxX14SparklineGroups ) // parameter validation if ws, err = f.parseFormatAddSparklineSet(sheet, opts); err != nil { return err } // Handle the sparkline type sparkType = "line" sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"} if opts.Type != "" { if specifiedSparkTypes, ok = sparkTypes[opts.Type]; !ok { err = ErrSparklineType return err } sparkType = specifiedSparkTypes } group = getSparklineGroupPresets()[opts.Style] group.Type = sparkType group.ColorAxis = &xlsxColor{RGB: "FF000000"} group.DisplayEmptyCellsAs = "gap" group.High = opts.High group.Low = opts.Low group.First = opts.First group.Last = opts.Last group.Negative = opts.Negative group.DisplayXAxis = opts.Axis group.Markers = opts.Markers if opts.SeriesColor != "" { group.ColorSeries = &xlsxColor{ RGB: getPaletteColor(opts.SeriesColor), } } if opts.Reverse { group.RightToLeft = opts.Reverse } f.addSparkline(opts, group) if err = f.appendSparkline(ws, group, groups); err != nil { return err } f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) return err } // parseFormatAddSparklineSet provides a function to validate sparkline // properties. func (f *File) parseFormatAddSparklineSet(sheet string, opts *SparklineOptions) (*xlsxWorksheet, error) { ws, err := f.workSheetReader(sheet) if err != nil { return ws, err } if opts == nil { return ws, ErrParameterRequired } if len(opts.Location) < 1 { return ws, ErrSparklineLocation } if len(opts.Range) < 1 { return ws, ErrSparklineRange } // The range and locations must match if len(opts.Location) != len(opts.Range) { return ws, ErrSparkline } if opts.Style < 0 || 35 < opts.Style { return ws, ErrSparklineStyle } if ws.ExtLst == nil { ws.ExtLst = &xlsxExtLst{} } return ws, err } // addSparkline provides a function to create a sparkline in a sparkline group // by given properties. func (f *File) addSparkline(opts *SparklineOptions, group *xlsxX14SparklineGroup) { for idx, location := range opts.Location { group.Sparklines.Sparkline = append(group.Sparklines.Sparkline, &xlsxX14Sparkline{ F: opts.Range[idx], Sqref: location, }) } } // appendSparkline provides a function to append sparkline to sparkline // groups. func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) error { var ( err error idx int appendMode bool decodeExtLst = new(decodeExtLst) decodeSparklineGroups *decodeX14SparklineGroups ext *xlsxExt sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte ) sparklineGroupBytes, _ = xml.Marshal(group) if ws.ExtLst != nil { // append mode ext if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for idx, ext = range decodeExtLst.Ext { if ext.URI == ExtURISparklineGroups { decodeSparklineGroups = new(decodeX14SparklineGroups) if err = f.xmlNewDecoder(strings.NewReader(ext.Content)). Decode(decodeSparklineGroups); err != nil && err != io.EOF { return err } if groups == nil { groups = &xlsxX14SparklineGroups{} } groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes) sparklineGroupsBytes, _ = xml.Marshal(groups) decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) appendMode = true } } } if !appendMode { sparklineGroupsBytes, _ = xml.Marshal(&xlsxX14SparklineGroups{ XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, SparklineGroups: []*xlsxX14SparklineGroup{group}, }) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes), }) } sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } ================================================ FILE: sparkline_test.go ================================================ package excelize import ( "fmt" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestAddSparkline(t *testing.T) { f, err := prepareSparklineDataset() assert.NoError(t, err) // Set the columns widths to make the output clearer style, err := f.NewStyle(&Style{Font: &Font{Bold: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style)) viewOpts, err := f.GetSheetView("Sheet1", 0) assert.NoError(t, err) viewOpts.ZoomScale = float64Ptr(150) assert.NoError(t, f.SetSheetView("Sheet1", 0, &viewOpts)) assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 14)) assert.NoError(t, f.SetColWidth("Sheet1", "B", "B", 50)) // Headings assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Sparkline")) assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Description")) assert.NoError(t, f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`)) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A2"}, Range: []string{"Sheet3!A1:J1"}, })) assert.NoError(t, f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`)) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A3"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", })) assert.NoError(t, f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`)) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A4"}, Range: []string{"Sheet3!A3:J3"}, Type: "win_loss", })) assert.NoError(t, f.SetCellValue("Sheet1", "B6", "Line with markers.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A6"}, Range: []string{"Sheet3!A1:J1"}, Markers: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B7", "Line with high and low points.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A7"}, Range: []string{"Sheet3!A1:J1"}, High: true, Low: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B8", "Line with first and last point markers.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A8"}, Range: []string{"Sheet3!A1:J1"}, First: true, Last: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B9", "Line with negative point markers.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A9"}, Range: []string{"Sheet3!A1:J1"}, Negative: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B10", "Line with axis.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A10"}, Range: []string{"Sheet3!A1:J1"}, Axis: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B12", "Column with default style (1).")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A12"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", })) assert.NoError(t, f.SetCellValue("Sheet1", "B13", "Column with style 2.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A13"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", Style: 2, })) assert.NoError(t, f.SetCellValue("Sheet1", "B14", "Column with style 3.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A14"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", Style: 3, })) assert.NoError(t, f.SetCellValue("Sheet1", "B15", "Column with style 4.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A15"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", Style: 4, })) assert.NoError(t, f.SetCellValue("Sheet1", "B16", "Column with style 5.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A16"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", Style: 5, })) assert.NoError(t, f.SetCellValue("Sheet1", "B17", "Column with style 6.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A17"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", Style: 6, })) assert.NoError(t, f.SetCellValue("Sheet1", "B18", "Column with a user defined color.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A18"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", SeriesColor: "E965E0", })) assert.NoError(t, f.SetCellValue("Sheet1", "B20", "A win/loss sparkline.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A20"}, Range: []string{"Sheet3!A3:J3"}, Type: "win_loss", })) assert.NoError(t, f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A21"}, Range: []string{"Sheet3!A3:J3"}, Type: "win_loss", Negative: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B23", "A left to right column (the default).")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A23"}, Range: []string{"Sheet3!A4:J4"}, Type: "column", Style: 20, })) assert.NoError(t, f.SetCellValue("Sheet1", "B24", "A right to left column.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A24"}, Range: []string{"Sheet3!A4:J4"}, Type: "column", Style: 20, Reverse: true, })) assert.NoError(t, f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A25"}, Range: []string{"Sheet3!A4:J4"}, Type: "column", Style: 20, })) assert.NoError(t, f.SetCellValue("Sheet1", "A25", "Growth")) assert.NoError(t, f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three.")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A27", "A28", "A29"}, Range: []string{"Sheet3!A5:J5", "Sheet3!A6:J6", "Sheet3!A7:J7"}, Markers: true, })) // Sheet2 sections assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Type: "win_loss", Negative: true, })) assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{ Location: []string{"F1"}, Range: []string{"Sheet2!A1:E1"}, Markers: true, })) assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{ Location: []string{"F2"}, Range: []string{"Sheet2!A2:E2"}, Type: "column", Style: 12, })) assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Type: "win_loss", Negative: true, })) // Save spreadsheet by the given path assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSparkline.xlsx"))) // Test error exceptions assert.EqualError(t, f.AddSparkline("SheetN", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, }), "sheet SheetN does not exist") assert.Equal(t, ErrParameterRequired, f.AddSparkline("Sheet1", nil)) // Test add sparkline with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.AddSparkline("Sheet:1", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Type: "win_loss", Negative: true, })) assert.Equal(t, ErrSparklineLocation, f.AddSparkline("Sheet1", &SparklineOptions{ Range: []string{"Sheet2!A3:E3"}, })) assert.Equal(t, ErrSparklineRange, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"F3"}, })) assert.Equal(t, ErrSparkline, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"F2", "F3"}, Range: []string{"Sheet2!A3:E3"}, })) assert.Equal(t, ErrSparklineType, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Type: "unknown_type", })) assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Style: -1, })) assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"F3"}, Range: []string{"Sheet2!A3:E3"}, Style: -1, })) // Test creating a conditional format with existing extension lists ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURISlicerListX14, ExtURISparklineGroups)} assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A3"}, Range: []string{"Sheet3!A2:J2"}, Type: "column", })) // Test creating a conditional format with invalid extension list characters ws.(*xlsxWorksheet).ExtLst.Ext = fmt.Sprintf(``, ExtURISparklineGroups) assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A2"}, Range: []string{"Sheet3!A1:J1"}, }), "XML syntax error on line 1: element closed by ") } func TestAppendSparkline(t *testing.T) { // Test unsupported charset. f := NewFile() ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)} assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8") } func prepareSparklineDataset() (*File, error) { f := NewFile() sheet2 := [][]int{ {-2, 2, 3, -1, 0}, {30, 20, 33, 20, 15}, {1, -1, -1, 1, -1}, } sheet3 := [][]int{ {-2, 2, 3, -1, 0, -2, 3, 2, 1, 0}, {30, 20, 33, 20, 15, 5, 5, 15, 10, 15}, {1, 1, -1, -1, 1, -1, 1, 1, 1, -1}, {5, 6, 7, 10, 15, 20, 30, 50, 70, 100}, {-2, 2, 3, -1, 0, -2, 3, 2, 1, 0}, {3, -1, 0, -2, 3, 2, 1, 0, 2, 1}, {0, -2, 3, 2, 1, 0, 1, 2, 3, 1}, } if _, err := f.NewSheet("Sheet2"); err != nil { return f, err } if _, err := f.NewSheet("Sheet3"); err != nil { return f, err } for row, data := range sheet2 { if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil { fmt.Println(err) } } for row, data := range sheet3 { if err := f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data); err != nil { fmt.Println(err) } } return f, nil } ================================================ FILE: stream.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "os" "reflect" "strconv" "strings" "time" ) // StreamWriter defined the type of stream writer. type StreamWriter struct { file *File Sheet string SheetID int sheetWritten bool worksheet *xlsxWorksheet rawData bufferedWriter rows int mergeCellsCount int mergeCells strings.Builder tableParts string } // NewStreamWriter returns stream writer struct by given worksheet name used for // writing data on a new existing empty worksheet with large amounts of data. // Note that after writing data with the stream writer for the worksheet, you // must call the 'Flush' method to end the streaming writing process, ensure // that the order of row numbers is ascending when set rows, and the normal // mode functions and stream mode functions can not be work mixed to writing // data on the worksheets. The stream writer will try to use temporary files on // disk to reduce the memory usage when in-memory chunks data over 16MB, and // you can't get cell value at this time. For example, set data for worksheet // of size 102400 rows x 50 columns with numbers and style: // // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // sw, err := f.NewStreamWriter("Sheet1") // if err != nil { // fmt.Println(err) // return // } // styleID, err := f.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "777777"}}) // if err != nil { // fmt.Println(err) // return // } // if err := sw.SetRow("A1", // []interface{}{ // excelize.Cell{StyleID: styleID, Value: "Data"}, // []excelize.RichTextRun{ // {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}}, // {Text: "Text", Font: &excelize.Font{Color: "e83723"}}, // }, // }, // excelize.RowOpts{Height: 45, Hidden: false}); err != nil { // fmt.Println(err) // return // } // for rowID := 2; rowID <= 102400; rowID++ { // row := make([]interface{}, 50) // for colID := 0; colID < 50; colID++ { // row[colID] = rand.Intn(640000) // } // cell, err := excelize.CoordinatesToCellName(1, rowID) // if err != nil { // fmt.Println(err) // break // } // if err := sw.SetRow(cell, row); err != nil { // fmt.Println(err) // break // } // } // if err := sw.Flush(); err != nil { // fmt.Println(err) // return // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // // Set cell value and cell formula for a worksheet with stream writer: // // err := sw.SetRow("A1", []interface{}{ // excelize.Cell{Value: 1}, // excelize.Cell{Value: 2}, // excelize.Cell{Formula: "SUM(A1,B1)"}}); // // Set cell value and rows style for a worksheet with stream writer: // // err := sw.SetRow("A1", []interface{}{ // excelize.Cell{Value: 1}}, // excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false}); func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) { if err := checkSheetName(sheet); err != nil { return nil, err } sheetID := f.getSheetID(sheet) if sheetID == -1 { return nil, ErrSheetNotExist{sheet} } sw := &StreamWriter{ file: f, Sheet: sheet, SheetID: sheetID, rawData: bufferedWriter{tmpDir: f.options.TmpDir}, } var err error sw.worksheet, err = f.workSheetReader(sheet) if err != nil { return nil, err } sheetXMLPath, _ := f.getSheetXMLPath(sheet) if f.streams == nil { f.streams = make(map[string]*StreamWriter) } f.streams[sheetXMLPath] = sw _, _ = sw.rawData.WriteString(xml.Header + ``, rID) if err = sw.file.addContentTypePart(tableID, "table"); err != nil { return err } b, _ := xml.Marshal(tbl) sw.file.saveFileList(tableXML, b) return err } // Extract values from a row in the StreamWriter. func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) { res = make([]string, vCol-hCol+1) r, err := sw.rawData.Reader() if err != nil { return nil, err } dec := sw.file.xmlNewDecoder(r) for { token, err := dec.Token() if err == io.EOF { return res, nil } if err != nil { return nil, err } startElement, ok := getRowElement(token, hRow) if !ok { continue } // decode cells var row xlsxRow if err := dec.DecodeElement(&row, &startElement); err != nil { return nil, err } for _, c := range row.C { col, _, err := CellNameToCoordinates(c.R) if err != nil { return nil, err } if col < hCol || col > vCol { continue } res[col-hCol], _ = c.getValueFrom(sw.file, nil, false) } return res, nil } } // Check if the token is an worksheet row with the matching row number. func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) { startElement, ok = token.(xml.StartElement) if !ok { return } ok = startElement.Name.Local == "row" if !ok { return } ok = false for _, attr := range startElement.Attr { if attr.Name.Local != "r" { continue } row, _ := strconv.Atoi(attr.Value) if row == hRow { ok = true return } } return } // Cell can be used directly in StreamWriter.SetRow to specify a style and // a value. type Cell struct { StyleID int Formula string Value interface{} } // RowOpts define the options for the set row, it can be used directly in // StreamWriter.SetRow to specify the style and properties of the row. type RowOpts struct { Height float64 Hidden bool StyleID int OutlineLevel int } // marshalAttrs prepare attributes of the row. func (r *RowOpts) marshalAttrs() (strings.Builder, error) { var ( err error attrs strings.Builder ) if r == nil { return attrs, err } if r.Height > MaxRowHeight { err = ErrMaxRowHeight return attrs, err } if r.OutlineLevel > 7 { err = ErrOutlineLevel return attrs, err } if r.StyleID > 0 { attrs.WriteString(` s="`) attrs.WriteString(strconv.Itoa(r.StyleID)) attrs.WriteString(`" customFormat="1"`) } if r.Height > 0 { attrs.WriteString(` ht="`) attrs.WriteString(strconv.FormatFloat(r.Height, 'f', -1, 64)) attrs.WriteString(`" customHeight="1"`) } if r.OutlineLevel > 0 { attrs.WriteString(` outlineLevel="`) attrs.WriteString(strconv.Itoa(r.OutlineLevel)) attrs.WriteString(`"`) } if r.Hidden { attrs.WriteString(` hidden="1"`) } return attrs, err } // parseRowOpts provides a function to parse the optional settings for // *StreamWriter.SetRow. func parseRowOpts(opts ...RowOpts) *RowOpts { options := &RowOpts{} for _, opt := range opts { options = &opt } return options } // SetRow writes an array to stream rows by giving starting cell reference and a // pointer to an array of values. Note that you must call the 'Flush' function // to end the streaming writing process. // // As a special case, if Cell is used as a value, then the Cell.StyleID will be // applied to that cell. func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpts) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } if row <= sw.rows { return newStreamSetRowError(row) } sw.rows = row sw.writeSheetData() options := parseRowOpts(opts...) attrs, err := options.marshalAttrs() if err != nil { return err } _, _ = sw.rawData.WriteString(``) for i, val := range values { if val == nil { continue } ref, err := CoordinatesToCellName(col+i, row) if err != nil { return err } c := xlsxC{R: ref, S: sw.worksheet.prepareCellStyle(col+i, row, options.StyleID)} var s int if v, ok := val.(Cell); ok { s, val = v.StyleID, v.Value setCellFormula(&c, v.Formula) } else if v, ok := val.(*Cell); ok && v != nil { s, val = v.StyleID, v.Value setCellFormula(&c, v.Formula) } if s > 0 { c.S = s } if err = sw.setCellValFunc(&c, val); err != nil { _, _ = sw.rawData.WriteString(``) return err } writeCell(&sw.rawData, c) } _, _ = sw.rawData.WriteString(``) return sw.rawData.Sync() } // SetColVisible provides a function set the visibility of a single column or // multiple columns for the StreamWriter. Note that you must call the // 'SetColVisible' function before the 'SetRow' function. // // For example hide column D: // // err := sw.SetColVisible(4, 4, false) // // Hide the columns from D to F (included): // // err := sw.SetColVisible(4, 6, false) func (sw *StreamWriter) SetColVisible(minVal, maxVal int, visible bool) error { if sw.sheetWritten { return newStreamSetRowOrderError("SetColVisible") } if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns { return ErrColumnNumber } if minVal > maxVal { minVal, maxVal = maxVal, minVal } sw.worksheet.setColVisible(minVal, maxVal, visible) return nil } // SetColOutlineLevel provides a function to set outline level of a single // column for the StreamWriter. The value of parameter 'level' is 1-7. Note that // you must call the 'SetColOutlineLevel' function before the 'SetRow' function. // For example, set outline level of column D to 2: // // err := sw.SetColOutlineLevel(4, 2) func (sw *StreamWriter) SetColOutlineLevel(col int, level uint8) error { if sw.sheetWritten { return newStreamSetRowOrderError("SetColOutlineLevel") } if col < MinColumns || col > MaxColumns { return ErrColumnNumber } if level > 7 || level < 1 { return ErrOutlineLevel } sw.worksheet.setColOutlineLevel(col, level) return nil } // SetColStyle provides a function to set the style of a single column or // multiple columns for the StreamWriter. Note that you must call // the 'SetColStyle' function before the 'SetRow' function. For example set // style of column H: // // err := sw.SetColStyle(8, 8, style) func (sw *StreamWriter) SetColStyle(minVal, maxVal, styleID int) error { if sw.sheetWritten { return newStreamSetRowOrderError("SetColStyle") } if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns { return ErrColumnNumber } if maxVal < minVal { minVal, maxVal = maxVal, minVal } s, err := sw.file.stylesReader() if err != nil { return err } if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { return newInvalidStyleID(styleID) } sw.worksheet.setColStyle(minVal, maxVal, styleID) return nil } // SetColWidth provides a function to set the width of a single column or // multiple columns for the StreamWriter. Note that you must call // the 'SetColWidth' function before the 'SetRow' function. For example set // the width column B:C as 20: // // err := sw.SetColWidth(2, 3, 20) func (sw *StreamWriter) SetColWidth(minVal, maxVal int, width float64) error { if sw.sheetWritten { return newStreamSetRowOrderError("SetColWidth") } if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns { return ErrColumnNumber } if width > MaxColumnWidth { return ErrColumnWidth } if minVal > maxVal { minVal, maxVal = maxVal, minVal } sw.worksheet.setColWidth(minVal, maxVal, width) return nil } // InsertPageBreak creates a page break to determine where the printed page ends // and where begins the next one by a given cell reference, the content before // the page break will be printed on one page and after the page break on // another. func (sw *StreamWriter) InsertPageBreak(cell string) error { return sw.worksheet.insertPageBreak(cell) } // SetPanes provides a function to create and remove freeze panes and split // panes by giving panes options for the StreamWriter. Note that you must call // the 'SetPanes' function before the 'SetRow' function. func (sw *StreamWriter) SetPanes(panes *Panes) error { if sw.sheetWritten { return newStreamSetRowOrderError("SetPanes") } return sw.worksheet.setPanes(panes) } // MergeCell provides a function to merge cells by a given range reference for // the StreamWriter. Don't create a merged cell that overlaps with another // existing merged cell. func (sw *StreamWriter) MergeCell(topLeftCell, bottomRightCell string) error { _, err := cellRefsToCoordinates(topLeftCell, bottomRightCell) if err != nil { return err } sw.mergeCellsCount++ _, _ = sw.mergeCells.WriteString(``) return nil } // setCellFormula provides a function to set formula of a cell. func setCellFormula(c *xlsxC, formula string) { if formula != "" { c.T, c.F = "str", &xlsxF{Content: formula} } } // setCellTime provides a function to set number of a cell with a time. func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error { var date1904, isNum bool wb, err := sw.file.workbookReader() if err != nil { return err } if wb != nil && wb.WorkbookPr != nil { date1904 = wb.WorkbookPr.Date1904 } if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 { style, _ := sw.file.NewStyle(&Style{NumFmt: 22}) c.S = style } return nil } // setCellValFunc provides a function to set value of a cell. func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error { var err error switch val := val.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: setCellIntFunc(c, val) case float32: c.setCellFloat(float64(val), -1, 32) case float64: c.setCellFloat(val, -1, 64) case string: c.setCellValue(val) case []byte: c.setCellValue(string(val)) case time.Duration: c.T, c.V = setCellDuration(val) case time.Time: err = sw.setCellTime(c, val) case bool: c.T, c.V = setCellBool(val) case nil: return err case []RichTextRun: c.T, c.IS = "inlineStr", &xlsxSI{} c.IS.R, err = setRichText(val) default: c.setCellValue(fmt.Sprint(val)) } return err } // setCellIntFunc is a wrapper of SetCellInt. func setCellIntFunc(c *xlsxC, val interface{}) { switch val := val.(type) { case int: c.T, c.V = setCellInt(int64(val)) case int8: c.T, c.V = setCellInt(int64(val)) case int16: c.T, c.V = setCellInt(int64(val)) case int32: c.T, c.V = setCellInt(int64(val)) case int64: c.T, c.V = setCellInt(val) case uint: c.T, c.V = setCellUint(uint64(val)) case uint8: c.T, c.V = setCellUint(uint64(val)) case uint16: c.T, c.V = setCellUint(uint64(val)) case uint32: c.T, c.V = setCellUint(uint64(val)) case uint64: c.T, c.V = setCellUint(val) } } // writeCell constructs a cell XML and writes it to the buffer. func writeCell(buf *bufferedWriter, c xlsxC) { _, _ = buf.WriteString(``) if c.F != nil { _, _ = buf.WriteString(``) _ = xml.EscapeText(buf, []byte(c.F.Content)) _, _ = buf.WriteString(``) } if c.V != "" { _, _ = buf.WriteString(``) _ = xml.EscapeText(buf, []byte(c.V)) _, _ = buf.WriteString(``) } if c.IS != nil { if len(c.IS.R) > 0 { is, _ := xml.Marshal(c.IS.R) _, _ = buf.WriteString(``) _, _ = buf.Write(is) _, _ = buf.WriteString(``) } if c.IS.T != nil { _, _ = buf.WriteString(``) _, _ = buf.Write([]byte(c.IS.T.Val)) _, _ = buf.WriteString(``) } } _, _ = buf.WriteString(``) } // writeSheetData prepares the element preceding sheetData and writes the // sheetData XML start element to the buffer. func (sw *StreamWriter) writeSheetData() { if !sw.sheetWritten { bulkAppendFields(&sw.rawData, sw.worksheet, 5, 6) if sw.worksheet.Cols != nil { _, _ = sw.rawData.WriteString("") for _, col := range sw.worksheet.Cols.Col { _, _ = sw.rawData.WriteString(`0 { _, _ = sw.rawData.WriteString(` outlineLevel="`) _, _ = sw.rawData.WriteString(strconv.FormatUint(uint64(col.OutlineLevel), 10)) _, _ = sw.rawData.WriteString(`"`) } _, _ = sw.rawData.WriteString(`/>`) } _, _ = sw.rawData.WriteString("") } _, _ = sw.rawData.WriteString(``) sw.sheetWritten = true } } // Flush ending the streaming writing process. func (sw *StreamWriter) Flush() error { sw.writeSheetData() _, _ = sw.rawData.WriteString(``) bulkAppendFields(&sw.rawData, sw.worksheet, 9, 16) mergeCells := strings.Builder{} if sw.mergeCellsCount > 0 { _, _ = mergeCells.WriteString(``) _, _ = mergeCells.WriteString(sw.mergeCells.String()) _, _ = mergeCells.WriteString(``) } _, _ = sw.rawData.WriteString(mergeCells.String()) bulkAppendFields(&sw.rawData, sw.worksheet, 18, 39) _, _ = sw.rawData.WriteString(sw.tableParts) bulkAppendFields(&sw.rawData, sw.worksheet, 41, 41) _, _ = sw.rawData.WriteString(``) if err := sw.rawData.Flush(); err != nil { return err } sheetPath := sw.file.sheetMap[sw.Sheet] sw.file.Sheet.Delete(sheetPath) sw.file.checked.Delete(sheetPath) sw.file.Pkg.Delete(sheetPath) return nil } // bulkAppendFields bulk-appends fields in a worksheet by specified field // names order range. func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) { s := reflect.ValueOf(ws).Elem() enc := xml.NewEncoder(w) for i := 0; i < s.NumField(); i++ { if from <= i && i <= to { _ = enc.Encode(s.Field(i).Interface()) } } } // bufferedWriter uses a temp file to store an extended buffer. Writes are // always made to an in-memory buffer, which will always succeed. The buffer // is written to the temp file with Sync, which may return an error. // Therefore, Sync should be periodically called and the error checked. type bufferedWriter struct { tmpDir string tmp *os.File buf bytes.Buffer } // Write to the in-memory buffer. The error is always nil. func (bw *bufferedWriter) Write(p []byte) (n int, err error) { return bw.buf.Write(p) } // WriteString write to the in-memory buffer. The error is always nil. func (bw *bufferedWriter) WriteString(p string) (n int, err error) { return bw.buf.WriteString(p) } // Reader provides read-access to the underlying buffer/file. func (bw *bufferedWriter) Reader() (io.Reader, error) { if bw.tmp == nil { return bytes.NewReader(bw.buf.Bytes()), nil } if err := bw.Flush(); err != nil { return nil, err } fi, err := bw.tmp.Stat() if err != nil { return nil, err } // os.File.ReadAt does not affect the cursor position and is safe to use here return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil } // Sync will write the in-memory buffer to a temp file, if the in-memory // buffer has grown large enough. Any error will be returned. func (bw *bufferedWriter) Sync() (err error) { // Try to use local storage if bw.buf.Len() < StreamChunkSize { return nil } if bw.tmp == nil { bw.tmp, err = os.CreateTemp(bw.tmpDir, "excelize-") if err != nil { // can not use local storage return nil } } return bw.Flush() } // Flush the entire in-memory buffer to the temp file, if a temp file is being // used. func (bw *bufferedWriter) Flush() error { if bw.tmp == nil { return nil } _, err := bw.buf.WriteTo(bw.tmp) if err != nil { return err } bw.buf.Reset() return nil } // Close the underlying temp file and reset the in-memory buffer. func (bw *bufferedWriter) Close() error { bw.buf.Reset() if bw.tmp == nil { return nil } defer func() { _ = os.Remove(bw.tmp.Name()) }() return bw.tmp.Close() } ================================================ FILE: stream_test.go ================================================ package excelize import ( "encoding/xml" "fmt" "io" "math" "math/rand" "os" "path/filepath" "strings" "testing" "time" "github.com/stretchr/testify/assert" ) func BenchmarkStreamWriter(b *testing.B) { file := NewFile() defer func() { if err := file.Close(); err != nil { b.Error(err) } }() row := make([]interface{}, 10) for colID := 0; colID < 10; colID++ { row[colID] = colID } for n := 0; n < b.N; n++ { streamWriter, _ := file.NewStreamWriter("Sheet1") for rowID := 10; rowID <= 110; rowID++ { cell, _ := CoordinatesToCellName(1, rowID) _ = streamWriter.SetRow(cell, row) } } b.ReportAllocs() } func TestStreamWriter(t *testing.T) { file := NewFile() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) // Test max characters in a cell row := make([]interface{}, 1) row[0] = strings.Repeat("c", TotalCellChars+2) assert.NoError(t, streamWriter.SetRow("A1", row)) // Test leading and ending space(s) character characters in a cell row = make([]interface{}, 1) row[0] = " characters" assert.NoError(t, streamWriter.SetRow("A2", row)) row = make([]interface{}, 1) row[0] = []byte("Word") assert.NoError(t, streamWriter.SetRow("A3", row)) // Test set cell with style and rich text styleID, err := file.NewStyle(&Style{Font: &Font{Color: "777777"}}) assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A4", []interface{}{ Cell{StyleID: styleID}, Cell{Formula: "SUM(A10,B10)", Value: " preserve space "}, }, RowOpts{Height: 45, StyleID: styleID})) assert.NoError(t, streamWriter.SetRow("A5", []interface{}{ &Cell{StyleID: styleID, Value: "cell <>&'\""}, &Cell{Formula: "SUM(A10,B10)"}, []RichTextRun{ {Text: "Rich ", Font: &Font{Color: "2354E8"}}, {Text: "Text", Font: &Font{Color: "E83723"}}, }, })) assert.NoError(t, streamWriter.SetRow("A6", []interface{}{time.Now()})) assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID})) assert.Equal(t, ErrMaxRowHeight, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1})) assert.NoError(t, streamWriter.SetRow("A9", []interface{}{math.NaN(), math.Inf(0), math.Inf(-1)})) for rowID := 10; rowID <= 51200; rowID++ { row := make([]interface{}, 50) for colID := 0; colID < 50; colID++ { row[colID] = rand.Intn(640000) } cell, _ := CoordinatesToCellName(1, rowID) assert.NoError(t, streamWriter.SetRow(cell, row)) } assert.NoError(t, streamWriter.Flush()) // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx"))) // Test set cell column overflow assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber) assert.NoError(t, file.Close()) // Test close temporary file error file = NewFile(Options{TmpDir: os.TempDir()}) streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) for rowID := 10; rowID <= 25600; rowID++ { row := make([]interface{}, 50) for colID := 0; colID < 50; colID++ { row[colID] = rand.Intn(640000) } cell, _ := CoordinatesToCellName(1, rowID) assert.NoError(t, streamWriter.SetRow(cell, row)) } assert.NoError(t, streamWriter.rawData.Close()) assert.Error(t, streamWriter.Flush()) streamWriter.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-") assert.NoError(t, err) _, err = streamWriter.rawData.Reader() assert.NoError(t, err) assert.NoError(t, streamWriter.rawData.tmp.Close()) assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) // Test create stream writer with unsupported charset file = NewFile() file.Sheet.Delete("xl/worksheets/sheet1.xml") file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err = file.NewStreamWriter("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, file.Close()) // Test read cell file = NewFile() streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{Cell{StyleID: styleID, Value: "Data"}})) assert.NoError(t, streamWriter.Flush()) cellValue, err := file.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "Data", cellValue) // Test stream reader for a worksheet with huge amounts of data file, err = OpenFile(filepath.Join("test", "TestStreamWriter.xlsx")) assert.NoError(t, err) rows, err := file.Rows("Sheet1") assert.NoError(t, err) cells := 0 for rows.Next() { row, err := rows.Columns() assert.NoError(t, err) cells += len(row) } assert.NoError(t, rows.Close()) assert.Equal(t, 2559562, cells) // Save spreadsheet with password. assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"})) assert.NoError(t, file.Close()) } func TestStreamSetColVisible(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetColVisible(3, 2, false)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColVisible(0, 3, false)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColVisible(MaxColumns+1, 3, false)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.Equal(t, newStreamSetRowOrderError("SetColVisible"), streamWriter.SetColVisible(2, 3, false)) assert.NoError(t, streamWriter.Flush()) } func TestStreamSetColOutlineLevel(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetColOutlineLevel(4, 2)) assert.Equal(t, ErrOutlineLevel, streamWriter.SetColOutlineLevel(4, 0)) assert.Equal(t, ErrOutlineLevel, streamWriter.SetColOutlineLevel(4, 8)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColOutlineLevel(0, 2)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColOutlineLevel(MaxColumns+1, 2)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.Equal(t, newStreamSetRowOrderError("SetColOutlineLevel"), streamWriter.SetColOutlineLevel(4, 2)) assert.NoError(t, streamWriter.Flush()) } func TestStreamSetColStyle(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetColStyle(3, 2, 0)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColStyle(0, 3, 20)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColStyle(MaxColumns+1, 3, 20)) assert.Equal(t, newInvalidStyleID(2), streamWriter.SetColStyle(1, 3, 2)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.Equal(t, newStreamSetRowOrderError("SetColStyle"), streamWriter.SetColStyle(2, 3, 0)) file = NewFile() defer func() { assert.NoError(t, file.Close()) }() // Test set column style with unsupported charset style sheet file.Styles = nil file.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.EqualError(t, streamWriter.SetColStyle(3, 2, 0), "XML syntax error on line 1: invalid UTF-8") } func TestStreamSetColWidth(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() styleID, err := file.NewStyle(&Style{ Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1}, }) if err != nil { fmt.Println(err) } streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetColWidth(3, 2, 20)) assert.NoError(t, streamWriter.SetColStyle(3, 2, styleID)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColWidth(0, 3, 20)) assert.Equal(t, ErrColumnNumber, streamWriter.SetColWidth(MaxColumns+1, 3, 20)) assert.Equal(t, ErrColumnWidth, streamWriter.SetColWidth(1, 3, MaxColumnWidth+1)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.Equal(t, newStreamSetRowOrderError("SetColWidth"), streamWriter.SetColWidth(2, 3, 20)) assert.NoError(t, streamWriter.Flush()) } func TestStreamSetPanes(t *testing.T) { file, paneOpts := NewFile(), &Panes{ Freeze: true, Split: false, XSplit: 1, YSplit: 0, TopLeftCell: "B1", ActivePane: "topRight", Selection: []Selection{ {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, }, } defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetPanes(paneOpts)) assert.Equal(t, ErrParameterInvalid, streamWriter.SetPanes(nil)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.Equal(t, newStreamSetRowOrderError("SetPanes"), streamWriter.SetPanes(paneOpts)) } func TestStreamTable(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) // Test add table without table header assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 2: unexpected EOF") // Write some rows. We want enough rows to force a temp file (>16MB) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) row := []interface{}{1, 2, 3} for r := 2; r < 10000; r++ { assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row)) } // Write a table assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C2"})) assert.NoError(t, streamWriter.Flush()) // Verify the table has names var table xlsxTable val, ok := file.Pkg.Load("xl/tables/table1.xml") assert.True(t, ok) assert.NoError(t, xml.Unmarshal(val.([]byte), &table)) assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name) assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C1"})) // Test add table with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.AddTable(&Table{Range: "A:B1"})) assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), streamWriter.AddTable(&Table{Range: "A1:B"})) // Test add table with invalid table name assert.Equal(t, newInvalidNameError("1Table"), streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"})) // Test add table with row number exceeds maximum limit assert.Equal(t, ErrMaxRows, streamWriter.AddTable(&Table{Range: "A1048576:C1048576"})) // Test add table with unsupported charset content types file.ContentTypes = nil file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 1: invalid UTF-8") } func TestStreamMergeCells(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.MergeCell("A1", "D1")) // Test merge cells with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.MergeCell("A", "D1")) assert.NoError(t, streamWriter.Flush()) // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamMergeCells.xlsx"))) } func TestStreamInsertPageBreak(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.InsertPageBreak("A1")) assert.NoError(t, streamWriter.Flush()) // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamInsertPageBreak.xlsx"))) } func TestNewStreamWriter(t *testing.T) { // Test error exceptions file := NewFile() defer func() { assert.NoError(t, file.Close()) }() _, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) _, err = file.NewStreamWriter("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test new stream write with invalid sheet name _, err = file.NewStreamWriter("Sheet:1") assert.Equal(t, ErrSheetNameInvalid, err) } func TestStreamMarshalAttrs(t *testing.T) { var r *RowOpts attrs, err := r.marshalAttrs() assert.NoError(t, err) assert.Empty(t, attrs) } func TestStreamSetRow(t *testing.T) { // Test error exceptions file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.SetRow("A", []interface{}{})) // Test set row with non-ascending row number assert.NoError(t, streamWriter.SetRow("A1", []interface{}{})) assert.Equal(t, newStreamSetRowError(1), streamWriter.SetRow("A1", []interface{}{})) // Test set row with unsupported charset workbook file.WorkBook = nil file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8") } func TestStreamSetRowNilValues(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}})) assert.NoError(t, streamWriter.Flush()) ws, err := file.workSheetReader("Sheet1") assert.NoError(t, err) assert.NotEqual(t, ws.SheetData.Row[0].C[0].XMLName.Local, "c") } func TestStreamSetRowWithStyle(t *testing.T) { file := NewFile() defer func() { assert.NoError(t, file.Close()) }() grayStyleID, err := file.NewStyle(&Style{Font: &Font{Color: "777777"}}) assert.NoError(t, err) blueStyleID, err := file.NewStyle(&Style{Font: &Font{Color: "0000FF"}}) assert.NoError(t, err) sheetName := "Sheet1" streamWriter, err := file.NewStreamWriter(sheetName) assert.NoError(t, err) assert.NoError(t, streamWriter.SetColStyle(1, 1, grayStyleID)) assert.NoError(t, streamWriter.SetColStyle(3, 3, blueStyleID)) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{ "A1", Cell{Value: "B1"}, &Cell{Value: "C1"}, Cell{StyleID: blueStyleID, Value: "D1"}, &Cell{StyleID: blueStyleID, Value: "E1"}, }, RowOpts{StyleID: grayStyleID})) assert.NoError(t, streamWriter.SetRow("A2", []interface{}{ "A2", Cell{Value: "B2"}, &Cell{Value: "C2"}, Cell{StyleID: grayStyleID, Value: "D2"}, &Cell{StyleID: blueStyleID, Value: "E2"}, })) assert.NoError(t, streamWriter.Flush()) ws, err := file.workSheetReader(sheetName) assert.NoError(t, err) for colIdx, expected := range []int{grayStyleID, grayStyleID, grayStyleID, blueStyleID, blueStyleID} { assert.Equal(t, expected, ws.SheetData.Row[0].C[colIdx].S) } for colIdx, expected := range []int{grayStyleID, 0, blueStyleID, grayStyleID, blueStyleID} { assert.Equal(t, expected, ws.SheetData.Row[1].C[colIdx].S) } } func TestStreamSetCellValFunc(t *testing.T) { f := NewFile() defer func() { assert.NoError(t, f.Close()) }() sw, err := f.NewStreamWriter("Sheet1") assert.NoError(t, err) c := &xlsxC{} for _, val := range []interface{}{ 128, int8(-128), int16(-32768), int32(-2147483648), int64(-9223372036854775808), uint(128), uint8(255), uint16(65535), uint32(4294967295), uint64(18446744073709551615), float32(100.1588), 100.1588, " Hello", []byte(" Hello"), time.Now().UTC(), time.Duration(1e13), true, nil, complex64(5 + 10i), } { assert.NoError(t, sw.setCellValFunc(c, val)) } } func TestStreamWriterOutlineLevel(t *testing.T) { file := NewFile() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) // Test set outlineLevel in row assert.NoError(t, streamWriter.SetRow("A1", nil, RowOpts{OutlineLevel: 1})) assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7})) assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8})) assert.NoError(t, streamWriter.Flush()) // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))) file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")) assert.NoError(t, err) for rowIdx, expected := range []uint8{1, 7, 0} { level, err := file.GetRowOutlineLevel("Sheet1", rowIdx+1) assert.NoError(t, err) assert.Equal(t, expected, level) } assert.NoError(t, file.Close()) } func TestStreamWriterReader(t *testing.T) { var ( err error sw = StreamWriter{ rawData: bufferedWriter{}, } ) sw.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-") assert.NoError(t, err) assert.NoError(t, sw.rawData.tmp.Close()) // Test reader stat a closed temp file _, err = sw.rawData.Reader() assert.Error(t, err) _, err = sw.getRowValues(1, 1, 1) assert.Error(t, err) assert.NoError(t, os.Remove(sw.rawData.tmp.Name())) sw = StreamWriter{ file: NewFile(), rawData: bufferedWriter{}, } // Test getRowValues without expected row sw.rawData.buf.WriteString("") _, err = sw.getRowValues(1, 1, 1) assert.NoError(t, err) sw.rawData.buf.Reset() // Test getRowValues with illegal cell reference sw.rawData.buf.WriteString("") _, err = sw.getRowValues(1, 1, 1) assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err) sw.rawData.buf.Reset() // Test getRowValues with invalid c element characters sw.rawData.buf.WriteString("") _, err = sw.getRowValues(1, 1, 1) assert.EqualError(t, err, "XML syntax error on line 1: element closed by ") sw.rawData.buf.Reset() } func TestStreamWriterGetRowElement(t *testing.T) { // Test get row element without r attribute dec := xml.NewDecoder(strings.NewReader("")) for { token, err := dec.Token() if err == io.EOF { break } _, ok := getRowElement(token, 0) assert.False(t, ok) } } ================================================ FILE: styles.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "math" "reflect" "sort" "strconv" "strings" ) // stylesReader provides a function to get the pointer to the structure after // deserialization of xl/styles.xml. func (f *File) stylesReader() (*xlsxStyleSheet, error) { if f.Styles == nil { f.Styles = new(xlsxStyleSheet) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))). Decode(f.Styles); err != nil && err != io.EOF { return f.Styles, err } } return f.Styles, nil } // styleSheetWriter provides a function to save xl/styles.xml after serialize // structure. func (f *File) styleSheetWriter() { if f.Styles != nil { output, _ := xml.Marshal(f.Styles) f.saveFileList(defaultXMLPathStyles, f.replaceNameSpaceBytes(defaultXMLPathStyles, output)) } } // themeWriter provides a function to save xl/theme/theme1.xml after serialize // structure. func (f *File) themeWriter() { newColor := func(c *decodeCTColor) xlsxCTColor { return xlsxCTColor{ ScrgbClr: c.ScrgbClr, SrgbClr: c.SrgbClr, HslClr: c.HslClr, SysClr: c.SysClr, SchemeClr: c.SchemeClr, PrstClr: c.PrstClr, } } newFontScheme := func(c *decodeFontCollection) xlsxFontCollection { return xlsxFontCollection{ Latin: c.Latin, Ea: c.Ea, Cs: c.Cs, Font: c.Font, ExtLst: c.ExtLst, } } if f.Theme != nil { output, _ := xml.Marshal(xlsxTheme{ XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value, Name: f.Theme.Name, ThemeElements: xlsxBaseStyles{ ClrScheme: xlsxColorScheme{ Name: f.Theme.ThemeElements.ClrScheme.Name, Dk1: newColor(&f.Theme.ThemeElements.ClrScheme.Dk1), Lt1: newColor(&f.Theme.ThemeElements.ClrScheme.Lt1), Dk2: newColor(&f.Theme.ThemeElements.ClrScheme.Dk2), Lt2: newColor(&f.Theme.ThemeElements.ClrScheme.Lt2), Accent1: newColor(&f.Theme.ThemeElements.ClrScheme.Accent1), Accent2: newColor(&f.Theme.ThemeElements.ClrScheme.Accent2), Accent3: newColor(&f.Theme.ThemeElements.ClrScheme.Accent3), Accent4: newColor(&f.Theme.ThemeElements.ClrScheme.Accent4), Accent5: newColor(&f.Theme.ThemeElements.ClrScheme.Accent5), Accent6: newColor(&f.Theme.ThemeElements.ClrScheme.Accent6), Hlink: newColor(&f.Theme.ThemeElements.ClrScheme.Hlink), FolHlink: newColor(&f.Theme.ThemeElements.ClrScheme.FolHlink), ExtLst: f.Theme.ThemeElements.ClrScheme.ExtLst, }, FontScheme: xlsxFontScheme{ Name: f.Theme.ThemeElements.FontScheme.Name, MajorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MajorFont), MinorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MinorFont), ExtLst: f.Theme.ThemeElements.FontScheme.ExtLst, }, FmtScheme: xlsxStyleMatrix{ Name: f.Theme.ThemeElements.FmtScheme.Name, FillStyleLst: f.Theme.ThemeElements.FmtScheme.FillStyleLst, LnStyleLst: f.Theme.ThemeElements.FmtScheme.LnStyleLst, EffectStyleLst: f.Theme.ThemeElements.FmtScheme.EffectStyleLst, BgFillStyleLst: f.Theme.ThemeElements.FmtScheme.BgFillStyleLst, }, ExtLst: f.Theme.ThemeElements.ExtLst, }, ObjectDefaults: f.Theme.ObjectDefaults, ExtraClrSchemeLst: f.Theme.ExtraClrSchemeLst, CustClrLst: f.Theme.CustClrLst, ExtLst: f.Theme.ExtLst, }) f.saveFileList(defaultXMLPathTheme, f.replaceNameSpaceBytes(defaultXMLPathTheme, output)) } } // sharedStringsWriter provides a function to save xl/sharedStrings.xml after // serialize structure. func (f *File) sharedStringsWriter() { if f.SharedStrings != nil { output, _ := xml.Marshal(f.SharedStrings) f.saveFileList(defaultXMLPathSharedStrings, f.replaceNameSpaceBytes(defaultXMLPathSharedStrings, output)) } } // parseFormatStyleSet provides a function to parse the format settings of the // cells and conditional formats. func parseFormatStyleSet(style *Style) (*Style, error) { var err error if style.Font != nil { if countUTF16String(style.Font.Family) > MaxFontFamilyLength { return style, ErrFontLength } if style.Font.Size > MaxFontSize { return style, ErrFontSize } } switch style.Fill.Type { case "gradient": if len(style.Fill.Color) != 2 { return style, ErrFillGradientColor } if style.Fill.Shading < 0 || style.Fill.Shading > 16 { return style, ErrFillGradientShading } case "pattern": if len(style.Fill.Color) > 1 { return style, ErrFillPatternColor } if style.Fill.Pattern < 0 || style.Fill.Pattern > 18 { return style, ErrFillPattern } case "": default: return style, ErrFillType } if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 { err = ErrCustomNumFmt } return style, err } // NewStyle provides a function to create the style for cells by a given style // options, and returns style index. The same style index can not be used // across different workbook. This function is concurrency safe. Note that // the 'Font.Color' field uses an RGB color represented in 'RRGGBB' hexadecimal // notation. // // The following table shows the border types used in 'Border.Type' supported by // excelize: // // Type | Description // --------------+------------------ // left | Left border // top | Top border // right | Right border // bottom | Bottom border // diagonalDown | Diagonal down border // diagonalUp | Diagonal up border // // The following table shows the border styles used in 'Border.Style' supported // by excelize index number: // // Index | Name | Weight | Style // -------+---------------+--------+------------- // 0 | None | 0 | // 1 | Continuous | 1 | ----------- // 2 | Continuous | 2 | ----------- // 3 | Dash | 1 | - - - - - - // 4 | Dot | 1 | . . . . . . // 5 | Continuous | 3 | ----------- // 6 | Double | 3 | =========== // 7 | Continuous | 0 | ----------- // 8 | Dash | 2 | - - - - - - // 9 | Dash Dot | 1 | - . - . - . // 10 | Dash Dot | 2 | - . - . - . // 11 | Dash Dot Dot | 1 | - . . - . . // 12 | Dash Dot Dot | 2 | - . . - . . // 13 | SlantDash Dot | 2 | / - . / - . // // The following table shows the border styles used in 'Border.Style' in the // order shown in the Excel dialog: // // Index | Style | Index | Style // -------+-------------+-------+------------- // 0 | None | 12 | - . . - . . // 7 | ----------- | 13 | / - . / - . // 4 | . . . . . . | 10 | - . - . - . // 11 | - . . - . . | 8 | - - - - - - // 9 | - . - . - . | 2 | ----------- // 3 | - - - - - - | 5 | ----------- // 1 | ----------- | 6 | =========== // // The 'Fill.Type' use to set fill type, supported types are 'gradient' or // 'pattern'. When configuring a fill of type 'gradient', a valid 'Fill.Shading' // value must be provided, and 'Fill.Color' must be an array containing exactly // two valid color values. When configuring a fill of type 'pattern', a valid // 'Fill.Pattern' value must be provided, and 'Fill.Color' must be an array // containing exactly one valid color value. // // The following table shows the shading styles used in 'Fill.Shading' supported // by excelize index number: // // Index | Style | Index | Style // -------+-----------------+-------+----------------- // 0-2 | Horizontal | 9-11 | Diagonal down // 3-5 | Vertical | 12-15 | From corner // 6-8 | Diagonal Up | 16 | From center // // The following table shows the pattern styles used in 'Fill.Pattern' supported // by excelize index number: // // Index | Style | Index | Style // -------+-----------------+-------+----------------- // 0 | None | 10 | darkTrellis // 1 | solid | 11 | lightHorizontal // 2 | mediumGray | 12 | lightVertical // 3 | darkGray | 13 | lightDown // 4 | lightGray | 14 | lightUp // 5 | darkHorizontal | 15 | lightGrid // 6 | darkVertical | 16 | lightTrellis // 7 | darkDown | 17 | gray125 // 8 | darkUp | 18 | gray0625 // 9 | darkGrid | | // // The 'Fill.Transparency' only use to set transparency for chart and shape, not // used for cell. The value of 'Fill.Transparency' should be a number from 0 to // 100, which represents 0% to 100%, The default value is 0, representing full // opaque (not transparent). // // The 'Alignment.Indent' is an integer value, where an increment of 1 // represents 3 spaces. Indicates the number of spaces (of the normal style // font) of indentation for text in a cell. The number of spaces to indent is // calculated as following: // // Number of spaces to indent = indent value * 3 // // For example, an indent value of 1 means that the text begins 3 space widths // (of the normal style font) from the edge of the cell. Note: The width of one // space character is defined by the font. Only left, right, and distributed // horizontal alignments are supported. // // The following table shows the type of cells' horizontal alignment used // in 'Alignment.Horizontal': // // Style // ------------------ // left // center // right // fill // justify // centerContinuous // distributed // // The following table shows the type of cells' vertical alignment used in // 'Alignment.Vertical': // // Style // ------------------ // top // center // justify // distributed // // The 'Alignment.ReadingOrder' is an uint64 value indicating whether the // reading order of the cell is left-to-right, right-to-left, or context // dependent. the valid value of this field was: // // Value | Description // -------+---------------------------------------------------- // 0 | Context Dependent - reading order is determined by scanning the // | text for the first non-whitespace character: if it is a strong // | right-to-left character, the reading order is right-to-left; // | otherwise, the reading order left-to-right. // 1 | Left-to-Right: reading order is left-to-right in the cell, as in // | English. // 2 | Right-to-Left: reading order is right-to-left in the cell, as in // | Hebrew. // // The 'Alignment.RelativeIndent' is an integer value to indicate the additional // number of spaces of indentation to adjust for text in a cell. // // The following table shows the type of font underline style used in // 'Font.Underline': // // Style // ------------------ // none // single // double // // NumFmt is used to set the built-in all languages formats index, built-in // language formats index, or built-in currency formats index, it doesn't work // when you specify the custom number format by CustomNumFmt. When you get // style definition by the GetStyle or GetConditionalStyle function, the NumFmt // only works if the number format code is exactly equal with any built-in all // languages format code, built-in language formats code, or built-in currency // format code. // // Excel's built-in all languages formats are shown in the following table: // // Index | Format String // -------+---------------------------------------------------- // 0 | General // 1 | 0 // 2 | 0.00 // 3 | #,##0 // 4 | #,##0.00 // 5 | ($#,##0_);($#,##0) // 6 | ($#,##0_);[Red]($#,##0) // 7 | ($#,##0.00_);($#,##0.00) // 8 | ($#,##0.00_);[Red]($#,##0.00) // 9 | 0% // 10 | 0.00% // 11 | 0.00E+00 // 12 | # ?/? // 13 | # ??/?? // 14 | m/d/yy // 15 | d-mmm-yy // 16 | d-mmm // 17 | mmm-yy // 18 | h:mm AM/PM // 19 | h:mm:ss AM/PM // 20 | h:mm // 21 | h:mm:ss // 22 | m/d/yy h:mm // ... | ... // 37 | (#,##0_);(#,##0) // 38 | (#,##0_);[Red](#,##0) // 39 | (#,##0.00_);(#,##0.00) // 40 | (#,##0.00_);[Red](#,##0.00) // 41 | _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_) // 42 | _($* #,##0_);_($* (#,##0);_($* "-"_);_(@_) // 43 | _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_) // 44 | _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_) // 45 | mm:ss // 46 | [h]:mm:ss // 47 | mm:ss.0 // 48 | ##0.0E+0 // 49 | @ // // Number format code in zh-tw language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-404]e/m/d // 28 | [$-404]e"年"m"月"d"日" // 29 | [$-404]e"年"m"月"d"日" // 30 | m/d/yy // 31 | yyyy"年"m"月"d"日" // 32 | hh"時"mm"分" // 33 | hh"時"mm"分"ss"秒" // 34 | 上午/下午 hh"時"mm"分" // 35 | 上午/下午 hh"時"mm"分"ss"秒" // 36 | [$-404]e/m/d // 50 | [$-404]e/m/d // 51 | [$-404]e"年"m"月"d"日" // 52 | 上午/下午 hh"時"mm"分" // 53 | 上午/下午 hh"時"mm"分"ss"秒" // 54 | [$-404]e"年"m"月"d"日" // 55 | 上午/下午 hh"時"mm"分" // 56 | 上午/下午 hh"時"mm"分"ss"秒" // 57 | [$-404]e/m/d // 58 | [$-404]e"年"m"月"d"日" // // Number format code in zh-cn language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"年"m"月" // 28 | m"月"d"日" // 29 | m"月"d"日" // 30 | m-d-yy // 31 | yyyy"年"m"月"d"日" // 32 | h"时"mm"分" // 33 | h"时"mm"分"ss"秒" // 34 | 上午/下午 h"时"mm"分" // 35 | 上午/下午 h"时"mm"分"ss"秒 // 36 | yyyy"年"m"月 // 50 | yyyy"年"m"月 // 51 | m"月"d"日 // 52 | yyyy"年"m"月 // 53 | m"月"d"日 // 54 | m"月"d"日 // 55 | 上午/下午 h"时"mm"分 // 56 | 上午/下午 h"时"mm"分"ss"秒 // 57 | yyyy"年"m"月 // 58 | m"月"d"日" // // Number format code in ja-jp language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-411]ge.m.d // 28 | [$-411]ggge"年"m"月"d"日 // 29 | [$-411]ggge"年"m"月"d"日 // 30 | m/d/y // 31 | yyyy"年"m"月"d"日 // 32 | h"時"mm"分 // 33 | h"時"mm"分"ss"秒 // 34 | yyyy"年"m"月 // 35 | m"月"d"日 // 36 | [$-411]ge.m.d // 50 | [$-411]ge.m.d // 51 | [$-411]ggge"年"m"月"d"日 // 52 | yyyy"年"m"月 // 53 | m"月"d"日 // 54 | [$-411]ggge"年"m"月"d"日 // 55 | yyyy"年"m"月 // 56 | m"月"d"日 // 57 | [$-411]ge.m.d // 58 | [$-411]ggge"年"m"月"d"日" // // Number format code in ko-kr language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"年" mm"月" dd"日 // 28 | mm-d // 29 | mm-d // 30 | mm-dd-y // 31 | yyyy"년" mm"월" dd"일 // 32 | h"시" mm"분 // 33 | h"시" mm"분" ss"초 // 34 | yyyy-mm-d // 35 | yyyy-mm-d // 36 | yyyy"年" mm"月" dd"日 // 50 | yyyy"年" mm"月" dd"日 // 51 | mm-d // 52 | yyyy-mm-d // 53 | yyyy-mm-d // 54 | mm-d // 55 | yyyy-mm-d // 56 | yyyy-mm-d // 57 | yyyy"年" mm"月" dd"日 // 58 | mm-dd // // Number format code in th-th language: // // Index | Symbol // -------+------------------------------------------- // 59 | t // 60 | t0.0 // 61 | t#,## // 62 | t#,##0.0 // 67 | t0 // 68 | t0.00 // 69 | t# ?/ // 70 | t# ??/? // 71 | ว/ด/ปปป // 72 | ว-ดดด-ป // 73 | ว-ดด // 74 | ดดด-ป // 75 | ช:น // 76 | ช:นน:ท // 77 | ว/ด/ปปปป ช:น // 78 | นน:ท // 79 | [ช]:นน:ท // 80 | นน:ทท. // 81 | d/m/bb // // Excelize built-in currency formats are shown in the following table, only // support these types in the following table (Index number is used only for // markup and is not used inside an Excel file and you can't get formatted value // by the function GetCellValue) currently: // // Index | Symbol // -------+--------------------------------------------------------------- // 164 | ¥ // 165 | $ English (United States) // 166 | $ Cherokee (United States) // 167 | $ Chinese (Singapore) // 168 | $ Chinese (Taiwan) // 169 | $ English (Australia) // 170 | $ English (Belize) // 171 | $ English (Canada) // 172 | $ English (Jamaica) // 173 | $ English (New Zealand) // 174 | $ English (Singapore) // 175 | $ English (Trinidad & Tobago) // 176 | $ English (U.S. Virgin Islands) // 177 | $ English (United States) // 178 | $ French (Canada) // 179 | $ Hawaiian (United States) // 180 | $ Malay (Brunei) // 181 | $ Quechua (Ecuador) // 182 | $ Spanish (Chile) // 183 | $ Spanish (Colombia) // 184 | $ Spanish (Ecuador) // 185 | $ Spanish (El Salvador) // 186 | $ Spanish (Mexico) // 187 | $ Spanish (Puerto Rico) // 188 | $ Spanish (United States) // 189 | $ Spanish (Uruguay) // 190 | £ English (United Kingdom) // 191 | £ Scottish Gaelic (United Kingdom) // 192 | £ Welsh (United Kindom) // 193 | ¥ Chinese (China) // 194 | ¥ Japanese (Japan) // 195 | ¥ Sichuan Yi (China) // 196 | ¥ Tibetan (China) // 197 | ¥ Uyghur (China) // 198 | ֏ Armenian (Armenia) // 199 | ؋ Pashto (Afghanistan) // 200 | ؋ Persian (Afghanistan) // 201 | ৳ Bengali (Bangladesh) // 202 | ៛ Khmer (Cambodia) // 203 | ₡ Spanish (Costa Rica) // 204 | ₦ Hausa (Nigeria) // 205 | ₦ Igbo (Nigeria) // 206 | ₩ Korean (South Korea) // 207 | ₪ Hebrew (Israel) // 208 | ₫ Vietnamese (Vietnam) // 209 | € Basque (Spain) // 210 | € Breton (France) // 211 | € Catalan (Spain) // 212 | € Corsican (France) // 213 | € Dutch (Belgium) // 214 | € Dutch (Netherlands) // 215 | € English (Ireland) // 216 | € Estonian (Estonia) // 217 | € Euro (€ 123) // 218 | € Euro (123 €) // 219 | € Finnish (Finland) // 220 | € French (Belgium) // 221 | € French (France) // 222 | € French (Luxembourg) // 223 | € French (Monaco) // 224 | € French (Réunion) // 225 | € Galician (Spain) // 226 | € German (Austria) // 227 | € German (German) // 228 | € German (Luxembourg) // 229 | € Greek (Greece) // 230 | € Inari Sami (Finland) // 231 | € Irish (Ireland) // 232 | € Italian (Italy) // 233 | € Latin (Italy) // 234 | € Latin, Serbian (Montenegro) // 235 | € Larvian (Latvia) // 236 | € Lithuanian (Lithuania) // 237 | € Lower Sorbian (Germany) // 238 | € Luxembourgish (Luxembourg) // 239 | € Maltese (Malta) // 240 | € Northern Sami (Finland) // 241 | € Occitan (France) // 242 | € Portuguese (Portugal) // 243 | € Serbian (Montenegro) // 244 | € Skolt Sami (Finland) // 245 | € Slovak (Slovakia) // 246 | € Slovenian (Slovenia) // 247 | € Spanish (Spain) // 248 | € Swedish (Finland) // 249 | € Swiss German (France) // 250 | € Upper Sorbian (Germany) // 251 | € Western Frisian (Netherlands) // 252 | ₭ Lao (Laos) // 253 | ₮ Mongolian (Mongolia) // 254 | ₮ Mongolian, Mongolian (Mongolia) // 255 | ₱ English (Philippines) // 256 | ₱ Filipino (Philippines) // 257 | ₴ Ukrainian (Ukraine) // 258 | ₸ Kazakh (Kazakhstan) // 259 | ₹ Arabic, Kashmiri (India) // 260 | ₹ English (India) // 261 | ₹ Gujarati (India) // 262 | ₹ Hindi (India) // 263 | ₹ Kannada (India) // 264 | ₹ Kashmiri (India) // 265 | ₹ Konkani (India) // 266 | ₹ Manipuri (India) // 267 | ₹ Marathi (India) // 268 | ₹ Nepali (India) // 269 | ₹ Oriya (India) // 270 | ₹ Punjabi (India) // 271 | ₹ Sanskrit (India) // 272 | ₹ Sindhi (India) // 273 | ₹ Tamil (India) // 274 | ₹ Urdu (India) // 275 | ₺ Turkish (Turkey) // 276 | ₼ Azerbaijani (Azerbaijan) // 277 | ₼ Cyrillic, Azerbaijani (Azerbaijan) // 278 | ₽ Russian (Russia) // 279 | ₽ Sakha (Russia) // 280 | ₾ Georgian (Georgia) // 281 | B/. Spanish (Panama) // 282 | Br Oromo (Ethiopia) // 283 | Br Somali (Ethiopia) // 284 | Br Tigrinya (Ethiopia) // 285 | Bs Quechua (Bolivia) // 286 | Bs Spanish (Bolivia) // 287 | BS. Spanish (Venezuela) // 288 | BWP Tswana (Botswana) // 289 | C$ Spanish (Nicaragua) // 290 | CA$ Latin, Inuktitut (Canada) // 291 | CA$ Mohawk (Canada) // 292 | CA$ Unified Canadian Aboriginal Syllabics, Inuktitut (Canada) // 293 | CFA French (Mali) // 294 | CFA French (Senegal) // 295 | CFA Fulah (Senegal) // 296 | CFA Wolof (Senegal) // 297 | CHF French (Switzerland) // 298 | CHF German (Liechtenstein) // 299 | CHF German (Switzerland) // 300 | CHF Italian (Switzerland) // 301 | CHF Romansh (Switzerland) // 302 | CLP Mapuche (Chile) // 303 | CN¥ Mongolian, Mongolian (China) // 304 | DZD Central Atlas Tamazight (Algeria) // 305 | FCFA French (Cameroon) // 306 | Ft Hungarian (Hungary) // 307 | G French (Haiti) // 308 | Gs. Spanish (Paraguay) // 309 | GTQ K'iche' (Guatemala) // 310 | HK$ Chinese (Hong Kong (China)) // 311 | HK$ English (Hong Kong (China)) // 312 | HRK Croatian (Croatia) // 313 | IDR English (Indonesia) // 314 | IQD Arbic, Central Kurdish (Iraq) // 315 | ISK Icelandic (Iceland) // 316 | K Burmese (Myanmar (Burma)) // 317 | Kč Czech (Czech Republic) // 318 | KM Bosnian (Bosnia & Herzegovina) // 319 | KM Croatian (Bosnia & Herzegovina) // 320 | KM Latin, Serbian (Bosnia & Herzegovina) // 321 | kr Faroese (Faroe Islands) // 322 | kr Northern Sami (Norway) // 323 | kr Northern Sami (Sweden) // 324 | kr Norwegian Bokmål (Norway) // 325 | kr Norwegian Nynorsk (Norway) // 326 | kr Swedish (Sweden) // 327 | kr. Danish (Denmark) // 328 | kr. Kalaallisut (Greenland) // 329 | Ksh Swahili (kenya) // 330 | L Romanian (Moldova) // 331 | L Russian (Moldova) // 332 | L Spanish (Honduras) // 333 | Lekë Albanian (Albania) // 334 | MAD Arabic, Central Atlas Tamazight (Morocco) // 335 | MAD French (Morocco) // 336 | MAD Tifinagh, Central Atlas Tamazight (Morocco) // 337 | MOP$ Chinese (Macau (China)) // 338 | MVR Divehi (Maldives) // 339 | Nfk Tigrinya (Eritrea) // 340 | NGN Bini (Nigeria) // 341 | NGN Fulah (Nigeria) // 342 | NGN Ibibio (Nigeria) // 343 | NGN Kanuri (Nigeria) // 344 | NOK Lule Sami (Norway) // 345 | NOK Southern Sami (Norway) // 346 | NZ$ Maori (New Zealand) // 347 | PKR Sindhi (Pakistan) // 348 | PYG Guarani (Paraguay) // 349 | Q Spanish (Guatemala) // 350 | R Afrikaans (South Africa) // 351 | R English (South Africa) // 352 | R Zulu (South Africa) // 353 | R$ Portuguese (Brazil) // 354 | RD$ Spanish (Dominican Republic) // 355 | RF Kinyarwanda (Rwanda) // 356 | RM English (Malaysia) // 357 | RM Malay (Malaysia) // 358 | RON Romanian (Romania) // 359 | Rp Indonesoan (Indonesia) // 360 | Rs Urdu (Pakistan) // 361 | Rs. Tamil (Sri Lanka) // 362 | RSD Latin, Serbian (Serbia) // 363 | RSD Serbian (Serbia) // 364 | RUB Bashkir (Russia) // 365 | RUB Tatar (Russia) // 366 | S/. Quechua (Peru) // 367 | S/. Spanish (Peru) // 368 | SEK Lule Sami (Sweden) // 369 | SEK Southern Sami (Sweden) // 370 | soʻm Latin, Uzbek (Uzbekistan) // 371 | soʻm Uzbek (Uzbekistan) // 372 | SYP Syriac (Syria) // 373 | THB Thai (Thailand) // 374 | TMT Turkmen (Turkmenistan) // 375 | US$ English (Zimbabwe) // 376 | ZAR Northern Sotho (South Africa) // 377 | ZAR Southern Sotho (South Africa) // 378 | ZAR Tsonga (South Africa) // 379 | ZAR Tswana (south Africa) // 380 | ZAR Venda (South Africa) // 381 | ZAR Xhosa (South Africa) // 382 | zł Polish (Poland) // 383 | ден Macedonian (Macedonia) // 384 | KM Cyrillic, Bosnian (Bosnia & Herzegovina) // 385 | KM Serbian (Bosnia & Herzegovina) // 386 | лв. Bulgarian (Bulgaria) // 387 | p. Belarusian (Belarus) // 388 | сом Kyrgyz (Kyrgyzstan) // 389 | сом Tajik (Tajikistan) // 390 | ج.م. Arabic (Egypt) // 391 | د.أ. Arabic (Jordan) // 392 | د.أ. Arabic (United Arab Emirates) // 393 | د.ب. Arabic (Bahrain) // 394 | د.ت. Arabic (Tunisia) // 395 | د.ج. Arabic (Algeria) // 396 | د.ع. Arabic (Iraq) // 397 | د.ك. Arabic (Kuwait) // 398 | د.ل. Arabic (Libya) // 399 | د.م. Arabic (Morocco) // 400 | ر Punjabi (Pakistan) // 401 | ر.س. Arabic (Saudi Arabia) // 402 | ر.ع. Arabic (Oman) // 403 | ر.ق. Arabic (Qatar) // 404 | ر.ي. Arabic (Yemen) // 405 | ریال Persian (Iran) // 406 | ل.س. Arabic (Syria) // 407 | ل.ل. Arabic (Lebanon) // 408 | ብር Amharic (Ethiopia) // 409 | रू Nepaol (Nepal) // 410 | රු. Sinhala (Sri Lanka) // 411 | ADP // 412 | AED // 413 | AFA // 414 | AFN // 415 | ALL // 416 | AMD // 417 | ANG // 418 | AOA // 419 | ARS // 420 | ATS // 421 | AUD // 422 | AWG // 423 | AZM // 424 | AZN // 425 | BAM // 426 | BBD // 427 | BDT // 428 | BEF // 429 | BGL // 430 | BGN // 431 | BHD // 432 | BIF // 433 | BMD // 434 | BND // 435 | BOB // 436 | BOV // 437 | BRL // 438 | BSD // 439 | BTN // 440 | BWP // 441 | BYR // 442 | BZD // 443 | CAD // 444 | CDF // 445 | CHE // 446 | CHF // 447 | CHW // 448 | CLF // 449 | CLP // 450 | CNY // 451 | COP // 452 | COU // 453 | CRC // 454 | CSD // 455 | CUC // 456 | CVE // 457 | CYP // 458 | CZK // 459 | DEM // 460 | DJF // 461 | DKK // 462 | DOP // 463 | DZD // 464 | ECS // 465 | ECV // 466 | EEK // 467 | EGP // 468 | ERN // 469 | ESP // 470 | ETB // 471 | EUR // 472 | FIM // 473 | FJD // 474 | FKP // 475 | FRF // 476 | GBP // 477 | GEL // 478 | GHC // 479 | GHS // 480 | GIP // 481 | GMD // 482 | GNF // 483 | GRD // 484 | GTQ // 485 | GYD // 486 | HKD // 487 | HNL // 488 | HRK // 489 | HTG // 490 | HUF // 491 | IDR // 492 | IEP // 493 | ILS // 494 | INR // 495 | IQD // 496 | IRR // 497 | ISK // 498 | ITL // 499 | JMD // 500 | JOD // 501 | JPY // 502 | KAF // 503 | KES // 504 | KGS // 505 | KHR // 506 | KMF // 507 | KPW // 508 | KRW // 509 | KWD // 510 | KYD // 511 | KZT // 512 | LAK // 513 | LBP // 514 | LKR // 515 | LRD // 516 | LSL // 517 | LTL // 518 | LUF // 519 | LVL // 520 | LYD // 521 | MAD // 522 | MDL // 523 | MGA // 524 | MGF // 525 | MKD // 526 | MMK // 527 | MNT // 528 | MOP // 529 | MRO // 530 | MTL // 531 | MUR // 532 | MVR // 533 | MWK // 534 | MXN // 535 | MXV // 536 | MYR // 537 | MZM // 538 | MZN // 539 | NAD // 540 | NGN // 541 | NIO // 542 | NLG // 543 | NOK // 544 | NPR // 545 | NTD // 546 | NZD // 547 | OMR // 548 | PAB // 549 | PEN // 550 | PGK // 551 | PHP // 552 | PKR // 553 | PLN // 554 | PTE // 555 | PYG // 556 | QAR // 557 | ROL // 558 | RON // 559 | RSD // 560 | RUB // 561 | RUR // 562 | RWF // 563 | SAR // 564 | SBD // 565 | SCR // 566 | SDD // 567 | SDG // 568 | SDP // 569 | SEK // 570 | SGD // 571 | SHP // 572 | SIT // 573 | SKK // 574 | SLL // 575 | SOS // 576 | SPL // 577 | SRD // 578 | SRG // 579 | STD // 580 | SVC // 581 | SYP // 582 | SZL // 583 | THB // 584 | TJR // 585 | TJS // 586 | TMM // 587 | TMT // 588 | TND // 589 | TOP // 590 | TRL // 591 | TRY // 592 | TTD // 593 | TWD // 594 | TZS // 595 | UAH // 596 | UGX // 597 | USD // 598 | USN // 599 | USS // 600 | UYI // 601 | UYU // 602 | UZS // 603 | VEB // 604 | VEF // 605 | VND // 606 | VUV // 607 | WST // 608 | XAF // 609 | XAG // 610 | XAU // 611 | XB5 // 612 | XBA // 613 | XBB // 614 | XBC // 615 | XBD // 616 | XCD // 617 | XDR // 618 | XFO // 619 | XFU // 620 | XOF // 621 | XPD // 622 | XPF // 623 | XPT // 624 | XTS // 625 | XXX // 626 | YER // 627 | YUM // 628 | ZAR // 629 | ZMK // 630 | ZMW // 631 | ZWD // 632 | ZWL // 633 | ZWN // 634 | ZWR // // Excelize support set custom number format for cell by CustomNumFmt field. For // example, set number as date type in Uruguay (Spanish) format for Sheet1!A6: // // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // if err := f.SetCellValue("Sheet1", "A6", 42920.5); err != nil { // fmt.Println(err) // return // } // exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" // style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp}) // if err != nil { // fmt.Println(err) // return // } // err = f.SetCellStyle("Sheet1", "A6", "A6", style) // // Cell Sheet1!A6 in the spreadsheet application: martes, 04 de Julio de 2017 // // DecimalPlaces is used to set the decimal places for built-in currency // formats, it doesn't work if you have specified the built-in all languages // formats or built-in language formats by NumFmt field, or specify the custom // number format by CustomNumFmt. When you get style definition by the GetStyle // or GetConditionalStyle function, the DecimalPlaces only doesn't nil if a // number format code has the same decimal places in the positive part negative // part, or only the positive part. func (f *File) NewStyle(style *Style) (int, error) { var ( fs *Style err error cellXfsID, fontID, borderID, fillID int ) if style == nil { return cellXfsID, err } fs, err = parseFormatStyleSet(style) if err != nil { return cellXfsID, err } if fs.DecimalPlaces != nil && (*fs.DecimalPlaces < 0 || *fs.DecimalPlaces > 30) { fs.DecimalPlaces = intPtr(2) } f.mu.Lock() s, err := f.stylesReader() if err != nil { f.mu.Unlock() return cellXfsID, err } f.mu.Unlock() s.mu.Lock() defer s.mu.Unlock() // check given style already exist. if cellXfsID, err = f.getStyleID(s, fs); err != nil || cellXfsID != -1 { return cellXfsID, err } numFmtID := newNumFmt(s, fs) if fs.Font != nil { fontID = f.getFontID(s, fs) if fontID == -1 { s.Fonts.Count++ s.Fonts.Font = append(s.Fonts.Font, fs.Font.newFont()) fontID = s.Fonts.Count - 1 } } borderID = getBorderID(s, fs) if borderID == -1 { if len(fs.Border) == 0 { borderID = 0 } else { s.Borders.Count++ s.Borders.Border = append(s.Borders.Border, newBorders(fs)) borderID = s.Borders.Count - 1 } } if fillID = getFillID(s, fs); fillID == -1 { if fill := newFills(fs, true); fill != nil { s.Fills.Count++ s.Fills.Fill = append(s.Fills.Fill, fill) fillID = s.Fills.Count - 1 } else { fillID = 0 } } applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs) applyProtection, protection := fs.Protection != nil, newProtection(fs) return setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection) } var ( // styleBorders list all types of the cell border style. styleBorders = []string{ "none", "thin", "medium", "dashed", "dotted", "thick", "double", "hair", "mediumDashed", "dashDot", "mediumDashDot", "dashDotDot", "mediumDashDotDot", "slantDashDot", } // styleBorderTypes list all types of the cell border. styleBorderTypes = []string{ "left", "right", "top", "bottom", "diagonalUp", "diagonalDown", } // styleFillPatterns list all types of the cell fill style. styleFillPatterns = []string{ "none", "solid", "mediumGray", "darkGray", "lightGray", "darkHorizontal", "darkVertical", "darkDown", "darkUp", "darkGrid", "darkTrellis", "lightHorizontal", "lightVertical", "lightDown", "lightUp", "lightGrid", "lightTrellis", "gray125", "gray0625", } // styleFillVariants list all preset variants of the fill style. styleFillVariants = func() []xlsxGradientFill { return []xlsxGradientFill{ {Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 270, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 180, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 255, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 315, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path"}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Left: 1, Right: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Top: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Left: 1, Right: 1, Top: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 0.5, Left: 0.5, Right: 0.5, Top: 0.5}, } } // getXfIDFuncs provides a function to get xfID by given style. getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{ "numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool { if style.CustomNumFmt == nil && numFmtID == -1 { return xf.NumFmtID != nil && *xf.NumFmtID == 0 } if style.NegRed || (style.DecimalPlaces != nil && *style.DecimalPlaces != 2) { return false } return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID }, "font": func(fontID int, xf xlsxXf, style *Style) bool { if style.Font == nil || fontID == 0 { return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont) } return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont }, "fill": func(fillID int, xf xlsxXf, style *Style) bool { if style.Fill.Type == "" || fillID == 0 { return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill) } return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill }, "border": func(borderID int, xf xlsxXf, style *Style) bool { if len(style.Border) == 0 { return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder) } return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder }, "alignment": func(ID int, xf xlsxXf, style *Style) bool { if style.Alignment == nil { return xf.ApplyAlignment == nil || !*xf.ApplyAlignment } return reflect.DeepEqual(xf.Alignment, newAlignment(style)) }, "protection": func(ID int, xf xlsxXf, style *Style) bool { if style.Protection == nil { return xf.ApplyProtection == nil || !*xf.ApplyProtection } return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection }, } // extractStyleCondFuncs provides a function set to returns if should be // extract style definition by given style. extractStyleCondFuncs = map[string]func(xlsxXf, *xlsxStyleSheet) bool{ "fill": func(xf xlsxXf, s *xlsxStyleSheet) bool { return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) && xf.FillID != nil && s.Fills != nil && *xf.FillID < len(s.Fills.Fill) }, "border": func(xf xlsxXf, s *xlsxStyleSheet) bool { return (xf.ApplyBorder == nil || (xf.ApplyBorder != nil && *xf.ApplyBorder)) && xf.BorderID != nil && s.Borders != nil && *xf.BorderID < len(s.Borders.Border) }, "font": func(xf xlsxXf, s *xlsxStyleSheet) bool { return (xf.ApplyFont == nil || (xf.ApplyFont != nil && *xf.ApplyFont)) && xf.FontID != nil && s.Fonts != nil && *xf.FontID < len(s.Fonts.Font) }, "alignment": func(xf xlsxXf, s *xlsxStyleSheet) bool { return xf.ApplyAlignment == nil || (xf.ApplyAlignment != nil && *xf.ApplyAlignment) }, "protection": func(xf xlsxXf, s *xlsxStyleSheet) bool { return xf.ApplyProtection == nil || (xf.ApplyProtection != nil && *xf.ApplyProtection) }, } // drawContFmtFunc defines functions to create conditional formats. drawContFmtFunc = map[string]func(p int, ct, ref, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){ "cellIs": drawCondFmtCellIs, "timePeriod": drawCondFmtTimePeriod, "text": drawCondFmtText, "top10": drawCondFmtTop10, "aboveAverage": drawCondFmtAboveAverage, "duplicateValues": drawCondFmtDuplicateUniqueValues, "uniqueValues": drawCondFmtDuplicateUniqueValues, "containsBlanks": drawCondFmtBlanks, "notContainsBlanks": drawCondFmtNoBlanks, "containsErrors": drawCondFmtErrors, "notContainsErrors": drawCondFmtNoErrors, "2_color_scale": drawCondFmtColorScale, "3_color_scale": drawCondFmtColorScale, "dataBar": drawCondFmtDataBar, "expression": drawCondFmtExp, "iconSet": drawCondFmtIconSet, } // extractContFmtFunc defines functions to get conditional formats. extractContFmtFunc = map[string]func(*File, string, *xlsxCfRule, *xlsxExtLst) ConditionalFormatOptions{ "cellIs": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtCellIs(c, extLst) }, "timePeriod": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtTimePeriod(c, ref, extLst) }, "containsText": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtText(c, extLst) }, "notContainsText": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtText(c, extLst) }, "beginsWith": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtText(c, extLst) }, "endsWith": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtText(c, extLst) }, "top10": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtTop10(c, extLst) }, "aboveAverage": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtAboveAverage(c, extLst) }, "duplicateValues": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtDuplicateUniqueValues(c, extLst) }, "uniqueValues": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtDuplicateUniqueValues(c, extLst) }, "containsBlanks": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtBlanks(c, extLst) }, "notContainsBlanks": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtNoBlanks(c, extLst) }, "containsErrors": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtErrors(c, extLst) }, "notContainsErrors": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtNoErrors(c, extLst) }, "colorScale": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtColorScale(c, extLst) }, "dataBar": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtDataBar(c, extLst) }, "expression": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtExp(c, extLst) }, "iconSet": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return f.extractCondFmtIconSet(c, extLst) }, } // validType defined the list of valid validation types. validType = map[string]string{ "cell": "cellIs", "average": "aboveAverage", "duplicate": "duplicateValues", "unique": "uniqueValues", "top": "top10", "bottom": "top10", "text": "text", "time_period": "timePeriod", "blanks": "containsBlanks", "no_blanks": "notContainsBlanks", "errors": "containsErrors", "no_errors": "notContainsErrors", "2_color_scale": "2_color_scale", "3_color_scale": "3_color_scale", "data_bar": "dataBar", "formula": "expression", "icon_set": "iconSet", } // criteriaType defined the list of valid criteria types. criteriaType = map[string]string{ "!=": "notEqual", "<": "lessThan", "<=": "lessThanOrEqual", "<>": "notEqual", "=": "equal", "==": "equal", ">": "greaterThan", ">=": "greaterThanOrEqual", "begins with": "beginsWith", "between": "between", "containing": "containsText", "continue month": "nextMonth", "continue week": "nextWeek", "ends with": "endsWith", "equal to": "equal", "greater than or equal to": "greaterThanOrEqual", "greater than": "greaterThan", "last 7 days": "last7Days", "last month": "lastMonth", "last week": "lastWeek", "less than or equal to": "lessThanOrEqual", "less than": "lessThan", "not between": "notBetween", "not containing": "notContains", "not equal to": "notEqual", "this month": "thisMonth", "this week": "thisWeek", "today": "today", "tomorrow": "tomorrow", "yesterday": "yesterday", } // operatorType defined the list of valid operator types. operatorType = map[string]string{ "beginsWith": "begins with", "between": "between", "containsText": "containing", "endsWith": "ends with", "equal": "equal to", "greaterThan": "greater than", "greaterThanOrEqual": "greater than or equal to", "last7Days": "last 7 days", "lastMonth": "last month", "lastWeek": "last week", "lessThan": "less than", "lessThanOrEqual": "less than or equal to", "nextMonth": "continue month", "nextWeek": "continue week", "notBetween": "not between", "notContains": "not containing", "notEqual": "not equal to", "thisMonth": "this month", "thisWeek": "this week", "today": "today", "tomorrow": "tomorrow", "yesterday": "yesterday", } // cellIsCriteriaType defined the list of valid criteria types used for // cellIs conditional formats. cellIsCriteriaType = []string{ "equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual", "containsText", "notContains", "beginsWith", "endsWith", } // cfvo3 defined the icon set conditional formatting rules. cfvo3 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "33"}, {Type: "percent", Val: "67"}, }}} // cfvo4 defined the icon set conditional formatting rules. cfvo4 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "25"}, {Type: "percent", Val: "50"}, {Type: "percent", Val: "75"}, }}} // cfvo5 defined the icon set conditional formatting rules. cfvo5 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "20"}, {Type: "percent", Val: "40"}, {Type: "percent", Val: "60"}, {Type: "percent", Val: "80"}, }}} // condFmtIconSetPresets defined the list of icon set conditional formatting // rules. condFmtIconSetPresets = map[string]*xlsxCfRule{ "3Arrows": cfvo3, "3ArrowsGray": cfvo3, "3Flags": cfvo3, "3Signs": cfvo3, "3Symbols": cfvo3, "3Symbols2": cfvo3, "3TrafficLights1": cfvo3, "3TrafficLights2": cfvo3, "4Arrows": cfvo4, "4ArrowsGray": cfvo4, "4Rating": cfvo4, "4RedToBlack": cfvo4, "4TrafficLights": cfvo4, "5Arrows": cfvo5, "5ArrowsGray": cfvo5, "5Quarters": cfvo5, "5Rating": cfvo5, } // cfvo3 defined the icon set conditional formatting rules. x14cfvo3 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{ {Type: "percent", F: "0"}, {Type: "percent", F: "33"}, {Type: "percent", F: "67"}, }}} // cfvo5 defined the icon set conditional formatting rules. x14cfvo5 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{ {Type: "percent", F: "0"}, {Type: "percent", F: "20"}, {Type: "percent", F: "40"}, {Type: "percent", F: "60"}, {Type: "percent", F: "80"}, }}} // condFmtX14IconSetPresets defined the list of icon set conditional // formatting rules which defined in worksheet extension list. condFmtX14IconSetPresets = map[string]*xlsxX14CfRule{ "3Stars": x14cfvo3, "3Triangles": x14cfvo3, "5Boxes": x14cfvo5, } ) // colorChoice returns a hex color code from the actual color values. func (clr *decodeCTColor) colorChoice() *string { if clr.SrgbClr != nil { return clr.SrgbClr.Val } if clr.SysClr != nil { return &clr.SysClr.LastClr } return nil } // GetBaseColor returns the preferred hex color code by giving hex color code, // indexed color, and theme color. func (f *File) GetBaseColor(hexColor string, indexedColor int, themeColor *int) string { if f.Theme != nil && themeColor != nil { clrScheme := f.Theme.ThemeElements.ClrScheme if val, ok := map[int]*string{ 0: clrScheme.Lt1.colorChoice(), 1: clrScheme.Dk1.colorChoice(), 2: clrScheme.Lt2.colorChoice(), 3: clrScheme.Dk2.colorChoice(), 4: clrScheme.Accent1.colorChoice(), 5: clrScheme.Accent2.colorChoice(), 6: clrScheme.Accent3.colorChoice(), 7: clrScheme.Accent4.colorChoice(), 8: clrScheme.Accent5.colorChoice(), 9: clrScheme.Accent6.colorChoice(), }[*themeColor]; ok && val != nil { return *val } } if len(hexColor) == 6 { return hexColor } if len(hexColor) == 8 { return strings.TrimPrefix(hexColor, "FF") } if f.Styles != nil && f.Styles.Colors != nil && f.Styles.Colors.IndexedColors != nil && indexedColor < len(f.Styles.Colors.IndexedColors.RgbColor) { return strings.TrimPrefix(f.Styles.Colors.IndexedColors.RgbColor[indexedColor].RGB, "FF") } if indexedColor < len(IndexedColorMapping) { return IndexedColorMapping[indexedColor] } return hexColor } // getThemeColor provides a function to convert theme color or index color to // RGB color. func (f *File) getThemeColor(clr *xlsxColor) string { var RGB string if clr == nil || f.Theme == nil { return RGB } if RGB = f.GetBaseColor(clr.RGB, clr.Indexed, clr.Theme); RGB != "" { RGB = strings.TrimPrefix(ThemeColor(RGB, clr.Tint), "FF") } return RGB } // extractBorders provides a function to extract borders styles settings by // given border styles definition. func (f *File) extractBorders(bdr *xlsxBorder, s *xlsxStyleSheet, style *Style) { if bdr != nil { var borders []Border extractBorder := func(lineType string, line *xlsxLine) { if line != nil && line.Style != "" { borders = append(borders, Border{ Type: lineType, Color: f.getThemeColor(line.Color), Style: inStrSlice(styleBorders, line.Style, false), }) } } for i, line := range []*xlsxLine{ bdr.Left, bdr.Right, bdr.Top, bdr.Bottom, bdr.Diagonal, bdr.Diagonal, } { if i < 4 { extractBorder(styleBorderTypes[i], line) } if i == 4 && bdr.DiagonalUp { extractBorder(styleBorderTypes[i], line) } if i == 5 && bdr.DiagonalDown { extractBorder(styleBorderTypes[i], line) } } style.Border = borders } } // extractFills provides a function to extract fill styles settings by // given fill styles definition. func (f *File) extractFills(fl *xlsxFill, s *xlsxStyleSheet, style *Style) { if fl != nil { var fill Fill if fl.GradientFill != nil { fill.Type = "gradient" for shading, variants := range styleFillVariants() { if fl.GradientFill.Bottom == variants.Bottom && fl.GradientFill.Degree == variants.Degree && fl.GradientFill.Left == variants.Left && fl.GradientFill.Right == variants.Right && fl.GradientFill.Top == variants.Top && fl.GradientFill.Type == variants.Type { fill.Shading = shading break } } for _, stop := range fl.GradientFill.Stop { fill.Color = append(fill.Color, f.getThemeColor(&stop.Color)) } } if fl.PatternFill != nil { fill.Type = "pattern" fill.Pattern = inStrSlice(styleFillPatterns, fl.PatternFill.PatternType, false) if fl.PatternFill.BgColor != nil { fill.Color = []string{f.getThemeColor(fl.PatternFill.BgColor)} } if fl.PatternFill.FgColor != nil { fill.Color = []string{f.getThemeColor(fl.PatternFill.FgColor)} } } style.Fill = fill } } // extractFont provides a function to extract font styles settings by given // font styles definition. func extractFont(fnt *xlsxFont) *Font { if fnt == nil { return nil } var font Font if fnt.Charset != nil { font.Charset = fnt.Charset.Val } font.Bold = fnt.B.Value() font.Italic = fnt.I.Value() if fnt.U != nil { if font.Underline = fnt.U.Value(); font.Underline == "" { font.Underline = "single" } } font.Family = fnt.Name.Value() font.Size = fnt.Sz.Value() font.Strike = fnt.Strike.Value() if fnt.Color != nil { font.Color = strings.TrimPrefix(fnt.Color.RGB, "FF") font.ColorIndexed = fnt.Color.Indexed font.ColorTheme = fnt.Color.Theme font.ColorTint = fnt.Color.Tint } font.VertAlign = fnt.VertAlign.Value() return &font } // extractNumFmt provides a function to extract number format by given styles // definition. func (f *File) extractNumFmt(n *int, s *xlsxStyleSheet, style *Style) { if n != nil { numFmtID := *n if builtInFmtCode, ok := builtInNumFmt[numFmtID]; ok || isLangNumFmt(numFmtID) { style.NumFmt = numFmtID if decimalPlaces := f.extractNumFmtDecimal(builtInFmtCode); decimalPlaces != -1 { style.DecimalPlaces = &decimalPlaces } return } if s.NumFmts != nil { for _, numFmt := range s.NumFmts.NumFmt { if numFmt.NumFmtID != numFmtID { continue } if decimalPlaces := f.extractNumFmtDecimal(numFmt.FormatCode); decimalPlaces != -1 { style.DecimalPlaces = &decimalPlaces } style.CustomNumFmt = &numFmt.FormatCode if strings.Contains(numFmt.FormatCode, ";[Red]") { style.NegRed = true } for numFmtID, fmtCode := range currencyNumFmt { if style.NegRed { fmtCode += ";[Red]" + fmtCode } if numFmt.FormatCode == fmtCode { style.NumFmt = numFmtID } } } } } } // extractAlignment provides a function to extract alignment format by // given style definition. func (f *File) extractAlignment(a *xlsxAlignment, s *xlsxStyleSheet, style *Style) { if a != nil { style.Alignment = &Alignment{ Horizontal: a.Horizontal, Indent: a.Indent, JustifyLastLine: a.JustifyLastLine, ReadingOrder: a.ReadingOrder, RelativeIndent: a.RelativeIndent, ShrinkToFit: a.ShrinkToFit, TextRotation: a.TextRotation, Vertical: a.Vertical, WrapText: a.WrapText, } } } // extractProtection provides a function to extract protection settings by // given format definition. func (f *File) extractProtection(p *xlsxProtection, s *xlsxStyleSheet, style *Style) { if p != nil { style.Protection = &Protection{} if p.Hidden != nil { style.Protection.Hidden = *p.Hidden } if p.Locked != nil { style.Protection.Locked = *p.Locked } } } // GetStyle provides a function to get style definition by given style index. func (f *File) GetStyle(idx int) (*Style, error) { var style *Style f.mu.Lock() s, err := f.stylesReader() if err != nil { return style, err } f.mu.Unlock() if idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx { return style, newInvalidStyleID(idx) } style = &Style{} xf := s.CellXfs.Xf[idx] if extractStyleCondFuncs["fill"](xf, s) { f.extractFills(s.Fills.Fill[*xf.FillID], s, style) } if extractStyleCondFuncs["border"](xf, s) { f.extractBorders(s.Borders.Border[*xf.BorderID], s, style) } if extractStyleCondFuncs["font"](xf, s) { style.Font = extractFont(s.Fonts.Font[*xf.FontID]) } if extractStyleCondFuncs["alignment"](xf, s) { f.extractAlignment(xf.Alignment, s, style) } if extractStyleCondFuncs["protection"](xf, s) { f.extractProtection(xf.Protection, s, style) } f.extractNumFmt(xf.NumFmtID, s, style) return style, nil } // getStyleID provides a function to get styleID by given style. If given // style does not exist, will return -1. func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) { var ( err error styleID = -1 ) if ss.CellXfs == nil { return styleID, err } numFmtID, borderID, fillID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style) fontID := f.getFontID(ss, style) if style.CustomNumFmt != nil { numFmtID = getCustomNumFmtID(ss, style) } for xfID, xf := range ss.CellXfs.Xf { if getXfIDFuncs["numFmt"](numFmtID, xf, style) && getXfIDFuncs["font"](fontID, xf, style) && getXfIDFuncs["fill"](fillID, xf, style) && getXfIDFuncs["border"](borderID, xf, style) && getXfIDFuncs["alignment"](0, xf, style) && getXfIDFuncs["protection"](0, xf, style) { styleID = xfID return styleID, err } } return styleID, err } // NewConditionalStyle provides a function to create style for conditional // format by given style format. The parameters are the same with the NewStyle // function. func (f *File) NewConditionalStyle(style *Style) (int, error) { f.mu.Lock() s, err := f.stylesReader() if err != nil { f.mu.Unlock() return 0, err } f.mu.Unlock() fs, err := parseFormatStyleSet(style) if err != nil { return 0, err } if fs.DecimalPlaces != nil && (*fs.DecimalPlaces < 0 || *fs.DecimalPlaces > 30) { fs.DecimalPlaces = intPtr(2) } dxf := xlsxDxf{ Fill: newFills(fs, false), } if fs.Alignment != nil { dxf.Alignment = newAlignment(fs) } if len(fs.Border) > 0 { dxf.Border = newBorders(fs) } if fs.Font != nil { dxf.Font = fs.Font.newFont() } if fs.Protection != nil { dxf.Protection = newProtection(fs) } dxf.NumFmt = newDxfNumFmt(s, style, &dxf) if s.Dxfs == nil { s.Dxfs = &xlsxDxfs{} } s.Dxfs.Count++ s.Dxfs.Dxfs = append(s.Dxfs.Dxfs, &dxf) return s.Dxfs.Count - 1, nil } // GetConditionalStyle returns conditional format style definition by specified // style index. func (f *File) GetConditionalStyle(idx int) (*Style, error) { var style *Style f.mu.Lock() s, err := f.stylesReader() if err != nil { return style, err } f.mu.Unlock() if idx < 0 || s.Dxfs == nil || len(s.Dxfs.Dxfs) <= idx { return style, newInvalidStyleID(idx) } style = &Style{} xf := s.Dxfs.Dxfs[idx] // The default pattern fill type of conditional format style is solid if xf.Fill != nil && xf.Fill.PatternFill != nil && xf.Fill.PatternFill.PatternType == "" { xf.Fill.PatternFill.PatternType = "solid" } f.extractFills(xf.Fill, s, style) f.extractBorders(xf.Border, s, style) style.Font = extractFont(xf.Font) f.extractAlignment(xf.Alignment, s, style) f.extractProtection(xf.Protection, s, style) if xf.NumFmt != nil { f.extractNumFmt(&xf.NumFmt.NumFmtID, s, style) } return style, nil } // newDxfNumFmt provides a function to create number format for conditional // format styles. func newDxfNumFmt(styleSheet *xlsxStyleSheet, style *Style, dxf *xlsxDxf) *xlsxNumFmt { dp, numFmtID := "0", 164 // Default custom number format code from 164. if style.DecimalPlaces != nil && *style.DecimalPlaces > 0 { dp += "." for i := 0; i < *style.DecimalPlaces; i++ { dp += "0" } } if style.CustomNumFmt != nil { if styleSheet.Dxfs != nil { for _, d := range styleSheet.Dxfs.Dxfs { if d != nil && d.NumFmt != nil && d.NumFmt.NumFmtID > numFmtID { numFmtID = d.NumFmt.NumFmtID } } } return &xlsxNumFmt{NumFmtID: numFmtID + 1, FormatCode: *style.CustomNumFmt} } numFmtCode, ok := builtInNumFmt[style.NumFmt] if style.NumFmt > 0 && ok { return &xlsxNumFmt{NumFmtID: style.NumFmt, FormatCode: numFmtCode} } fc, currency := currencyNumFmt[style.NumFmt] if !currency { return nil } if style.DecimalPlaces != nil { fc = strings.ReplaceAll(fc, "0.00", dp) } if style.NegRed { fc = fc + ";[Red]" + fc } return &xlsxNumFmt{NumFmtID: numFmtID, FormatCode: fc} } // GetDefaultFont provides the default font name currently set in the // workbook. The spreadsheet generated by excelize default font is Calibri. func (f *File) GetDefaultFont() (string, error) { font, err := f.readDefaultFont() if err != nil { return "", err } return *font.Name.Val, err } // SetDefaultFont changes the default font in the workbook. func (f *File) SetDefaultFont(fontName string) error { font, err := f.readDefaultFont() if err != nil { return err } font.Name.Val = stringPtr(fontName) f.mu.Lock() s, _ := f.stylesReader() f.mu.Unlock() s.Fonts.Font[0] = font custom := true s.CellStyles.CellStyle[0].CustomBuiltIn = &custom return err } // readDefaultFont provides an un-marshalled font value. func (f *File) readDefaultFont() (*xlsxFont, error) { f.mu.Lock() defer f.mu.Unlock() s, err := f.stylesReader() if err != nil { return nil, err } return s.Fonts.Font[0], err } // getFontID provides a function to get font ID. // If given font does not exist, will return -1. func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) int { fontID := -1 if styleSheet.Fonts == nil || style.Font == nil { return fontID } for idx, fnt := range styleSheet.Fonts.Font { if reflect.DeepEqual(*fnt, *style.Font.newFont()) { fontID = idx return fontID } } return fontID } // newFontColor set font color by given styles. func newFontColor(font *Font) *xlsxColor { var fontColor *xlsxColor prepareFontColor := func() { if fontColor != nil { return } fontColor = &xlsxColor{} } if font.Color != "" { prepareFontColor() fontColor.RGB = getPaletteColor(font.Color) } if font.ColorIndexed >= 0 && font.ColorIndexed <= len(IndexedColorMapping)+1 { prepareFontColor() fontColor.Indexed = font.ColorIndexed } if font.ColorTheme != nil { prepareFontColor() fontColor.Theme = font.ColorTheme } if font.ColorTint != 0 { prepareFontColor() fontColor.Tint = font.ColorTint } return fontColor } // newFont provides a function to add font style by given cell format // settings. func (fnt *Font) newFont() *xlsxFont { font := xlsxFont{Family: &attrValInt{Val: intPtr(2)}} if fnt.Size >= MinFontSize { font.Sz = &attrValFloat{Val: float64Ptr(fnt.Size)} } if fnt.Family != "" { font.Name = &attrValString{Val: stringPtr(fnt.Family)} } if fnt.Charset != nil { font.Charset = &attrValInt{Val: fnt.Charset} } font.Color = newFontColor(fnt) if fnt.Bold { font.B = &attrValBool{Val: &fnt.Bold} } if fnt.Italic { font.I = &attrValBool{Val: &fnt.Italic} } if fnt.Strike { font.Strike = &attrValBool{Val: &fnt.Strike} } if idx := inStrSlice(supportedUnderlineTypes, fnt.Underline, true); idx != -1 { font.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])} } if inStrSlice(supportedVertAlignTypes, fnt.VertAlign, true) != -1 { font.VertAlign = &attrValString{Val: &fnt.VertAlign} } return &font } // getNumFmtID provides a function to get number format code ID. // If given number format code does not exist, will return -1. func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) int { numFmtID := -1 if _, ok := builtInNumFmt[style.NumFmt]; ok { return style.NumFmt } if (27 <= style.NumFmt && style.NumFmt <= 36) || (50 <= style.NumFmt && style.NumFmt <= 81) { return style.NumFmt } if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok { numFmtID = style.NumFmt if styleSheet.NumFmts != nil { for _, numFmt := range styleSheet.NumFmts.NumFmt { if numFmt.FormatCode == fmtCode { return numFmt.NumFmtID } } } } return numFmtID } // newNumFmt provides a function to check if number format code in the range // of built-in values. func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { dp, numFmtID := "0", 164 // Default custom number format code from 164. if style.DecimalPlaces != nil && *style.DecimalPlaces > 0 { dp += "." for i := 0; i < *style.DecimalPlaces; i++ { dp += "0" } } if style.CustomNumFmt != nil { if customNumFmtID := getCustomNumFmtID(styleSheet, style); customNumFmtID != -1 { return customNumFmtID } return setCustomNumFmt(styleSheet, style) } if _, ok := builtInNumFmt[style.NumFmt]; !ok { fc, currency := currencyNumFmt[style.NumFmt] if !currency { return setLangNumFmt(style) } if style.DecimalPlaces != nil { fc = strings.ReplaceAll(fc, "0.00", dp) } if style.NegRed { fc = fc + ";[Red]" + fc } if styleSheet.NumFmts == nil { styleSheet.NumFmts = &xlsxNumFmts{NumFmt: []*xlsxNumFmt{}} } else { numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 } styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &xlsxNumFmt{ FormatCode: fc, NumFmtID: numFmtID, }) styleSheet.NumFmts.Count++ return numFmtID } return style.NumFmt } // setCustomNumFmt provides a function to set custom number format code. func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { nf := xlsxNumFmt{NumFmtID: 163, FormatCode: *style.CustomNumFmt} if styleSheet.NumFmts == nil { styleSheet.NumFmts = &xlsxNumFmts{} } for _, numFmt := range styleSheet.NumFmts.NumFmt { if numFmt != nil && nf.NumFmtID < numFmt.NumFmtID { nf.NumFmtID = numFmt.NumFmtID } } nf.NumFmtID++ styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) styleSheet.NumFmts.Count = len(styleSheet.NumFmts.NumFmt) return nf.NumFmtID } // getCustomNumFmtID provides a function to get custom number format code ID. // If given custom number format code does not exist, will return -1. func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID int) { customNumFmtID = -1 if styleSheet.NumFmts == nil { return } for _, numFmt := range styleSheet.NumFmts.NumFmt { if style.CustomNumFmt != nil && numFmt.FormatCode == *style.CustomNumFmt { customNumFmtID = numFmt.NumFmtID return } } return } // isLangNumFmt provides a function to returns if a given number format ID is a // built-in language glyphs number format code. func isLangNumFmt(ID int) bool { return (27 <= ID && ID <= 36) || (50 <= ID && ID <= 62) || (67 <= ID && ID <= 81) } // setLangNumFmt provides a function to set number format code with language. func setLangNumFmt(style *Style) int { if isLangNumFmt(style.NumFmt) { return style.NumFmt } return 0 } // getFillID provides a function to get fill ID. If given fill is not // exist, will return -1. func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) { fillID = -1 if styleSheet.Fills == nil || style.Fill.Type == "" { return } fills := newFills(style, true) if fills == nil { return } for idx, fill := range styleSheet.Fills.Fill { if reflect.DeepEqual(fill, fills) { fillID = idx return } } return } // newFills provides a function to add fill elements in the styles.xml by // given cell format settings. func newFills(style *Style, fg bool) *xlsxFill { var fill xlsxFill switch style.Fill.Type { case "gradient": gradient := styleFillVariants()[style.Fill.Shading] gradient.Stop[0].Color.RGB = getPaletteColor(style.Fill.Color[0]) gradient.Stop[1].Color.RGB = getPaletteColor(style.Fill.Color[1]) if len(gradient.Stop) == 3 { gradient.Stop[2].Color.RGB = getPaletteColor(style.Fill.Color[0]) } fill.GradientFill = &gradient case "pattern": var pattern xlsxPatternFill pattern.PatternType = styleFillPatterns[style.Fill.Pattern] if len(style.Fill.Color) < 1 { fill.PatternFill = &pattern break } if fg { if pattern.FgColor == nil { pattern.FgColor = new(xlsxColor) } pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0]) } else { if pattern.BgColor == nil { pattern.BgColor = new(xlsxColor) } pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0]) } fill.PatternFill = &pattern default: return nil } return &fill } // newAlignment provides a function to formatting information pertaining to // text alignment in cells. There are a variety of choices for how text is // aligned both horizontally and vertically, as well as indentation settings, // and so on. func newAlignment(style *Style) *xlsxAlignment { var alignment xlsxAlignment if style.Alignment != nil { alignment.Horizontal = style.Alignment.Horizontal alignment.Indent = style.Alignment.Indent alignment.JustifyLastLine = style.Alignment.JustifyLastLine alignment.ReadingOrder = style.Alignment.ReadingOrder alignment.RelativeIndent = style.Alignment.RelativeIndent alignment.ShrinkToFit = style.Alignment.ShrinkToFit alignment.TextRotation = style.Alignment.TextRotation alignment.Vertical = style.Alignment.Vertical alignment.WrapText = style.Alignment.WrapText } return &alignment } // newProtection provides a function to set protection properties associated // with the cell. func newProtection(style *Style) *xlsxProtection { var protection xlsxProtection if style.Protection != nil { protection.Hidden = &style.Protection.Hidden protection.Locked = &style.Protection.Locked } return &protection } // getBorderID provides a function to get border ID. If given border is not // exist, will return -1. func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) { borderID = -1 if styleSheet.Borders == nil || len(style.Border) == 0 { return } for idx, border := range styleSheet.Borders.Border { if reflect.DeepEqual(*border, *newBorders(style)) { borderID = idx return } } return } // newBorders provides a function to add border elements in the styles.xml by // given borders format settings. func newBorders(style *Style) *xlsxBorder { var border xlsxBorder for _, v := range style.Border { if 0 <= v.Style && v.Style < 14 { line := &xlsxLine{Style: styleBorders[v.Style], Color: &xlsxColor{RGB: getPaletteColor(v.Color)}} switch v.Type { case "left": border.Left = line case "right": border.Right = line case "top": border.Top = line case "bottom": border.Bottom = line case "diagonalUp": border.Diagonal, border.DiagonalUp = line, true case "diagonalDown": border.Diagonal, border.DiagonalDown = line, true } } } return &border } // setCellXfs provides a function to set describes all the formatting for a // cell. func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) { var xf xlsxXf xf.FontID = intPtr(fontID) if fontID != 0 { xf.ApplyFont = boolPtr(true) } xf.NumFmtID = intPtr(numFmtID) if numFmtID != 0 { xf.ApplyNumberFormat = boolPtr(true) } xf.FillID = intPtr(fillID) if fillID != 0 { xf.ApplyFill = boolPtr(true) } xf.BorderID = intPtr(borderID) if borderID != 0 { xf.ApplyBorder = boolPtr(true) } if len(style.CellXfs.Xf) == MaxCellStyles { return 0, ErrCellStyles } style.CellXfs.Count = len(style.CellXfs.Xf) + 1 xf.Alignment = alignment if alignment != nil { xf.ApplyAlignment = boolPtr(applyAlignment) } if applyProtection { xf.ApplyProtection = boolPtr(applyProtection) xf.Protection = protection } xfID := 0 xf.XfID = &xfID style.CellXfs.Xf = append(style.CellXfs.Xf, xf) return style.CellXfs.Count - 1, nil } // GetCellStyle provides a function to get cell style index by given worksheet // name and cell reference. This function is concurrency safe. func (f *File) GetCellStyle(sheet, cell string) (int, error) { f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return 0, err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() col, row, err := CellNameToCoordinates(cell) if err != nil { return 0, err } ws.prepareSheetXML(col, row) return ws.prepareCellStyle(col, row, ws.SheetData.Row[row-1].C[col-1].S), err } // SetCellStyle provides a function to add style attribute for cells by given // worksheet name, range reference and style ID. This function is concurrency // safe. Note that diagonalDown and diagonalUp type border should be use same // color in the same range. SetCellStyle will overwrite the existing // styles for the cell, it won't append or merge style with existing styles. // // For example create a borders of cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Border: []excelize.Border{ // {Type: "left", Color: "0000FF", Style: 3}, // {Type: "top", Color: "00FF00", Style: 4}, // {Type: "bottom", Color: "FFFF00", Style: 5}, // {Type: "right", Color: "FF0000", Style: 6}, // {Type: "diagonalDown", Color: "A020F0", Style: 7}, // {Type: "diagonalUp", Color: "A020F0", Style: 8}, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set gradient fill with vertical variants shading styles for cell H9 on // Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Fill: excelize.Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 1}, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set solid style pattern fill for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Fill: excelize.Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1}, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set alignment style for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Alignment: &excelize.Alignment{ // Horizontal: "center", // Indent: 1, // JustifyLastLine: true, // ReadingOrder: 0, // RelativeIndent: 1, // ShrinkToFit: true, // TextRotation: 45, // Vertical: "", // WrapText: true, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Dates and times in Excel are represented by real numbers, for example "Apr 7 // 2017 12:00 PM" is represented by the number 42920.5. Set date and time format // for cell H9 on Sheet1: // // f.SetCellValue("Sheet1", "H9", 42920.5) // style, err := f.NewStyle(&excelize.Style{NumFmt: 22}) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set font style for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Font: &excelize.Font{ // Bold: true, // Italic: true, // Family: "Times New Roman", // Size: 36, // Color: "777777", // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Hide and lock for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Protection: &excelize.Protection{ // Hidden: true, // Locked: true, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID int) error { hCol, hRow, err := CellNameToCoordinates(topLeftCell) if err != nil { return err } vCol, vRow, err := CellNameToCoordinates(bottomRightCell) if err != nil { return err } // Normalize the range, such correct C1:B3 to B1:C3. if vCol < hCol { vCol, hCol = hCol, vCol } if vRow < hRow { vRow, hRow = hRow, vRow } hColIdx := hCol - 1 hRowIdx := hRow - 1 vColIdx := vCol - 1 vRowIdx := vRow - 1 f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { f.mu.Unlock() return err } s, err := f.stylesReader() if err != nil { f.mu.Unlock() return err } f.mu.Unlock() ws.mu.Lock() defer ws.mu.Unlock() ws.prepareSheetXML(vCol, vRow) ws.makeContiguousColumns(hRow, vRow, vCol) if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { return newInvalidStyleID(styleID) } for r := hRowIdx; r <= vRowIdx; r++ { for k := hColIdx; k <= vColIdx; k++ { ws.SheetData.Row[r].C[k].S = styleID } } return err } // SetConditionalFormat provides a function to create conditional formatting // rule for cell value. Conditional formatting is a feature of Excel which // allows you to apply a format to a cell or a range of cells based on certain // criteria. // // The type option is a required parameter and it has no default value. // Allowable type values and their associated parameters are: // // Type | Parameters // ---------------+------------------------------------ // cell | Criteria // | Value // | MinValue // | MaxValue // time_period | Criteria // text | Criteria // | Value // average | Criteria // duplicate | (none) // unique | (none) // top | Criteria // | Value // bottom | Criteria // | Value // blanks | (none) // no_blanks | (none) // errors | (none) // no_errors | (none) // 2_color_scale | MinType // | MaxType // | MinValue // | MaxValue // | MinColor // | MaxColor // 3_color_scale | MinType // | MidType // | MaxType // | MinValue // | MidValue // | MaxValue // | MinColor // | MidColor // | MaxColor // data_bar | MinType // | MaxType // | MinValue // | MaxValue // | BarBorderColor // | BarColor // | BarDirection // | BarOnly // | BarSolid // icon_set | IconStyle // | ReverseIcons // | IconsOnly // formula | Criteria // // The 'Criteria' parameter is used to set the criteria by which the cell data // will be evaluated. It has no default value. The most common criteria as // applied to {Type: "cell"} are: // // between | // not between | // equal to | == // not equal to | != // greater than | > // less than | < // greater than or equal to | >= // less than or equal to | <= // // You can either use Excel's textual description strings, in the first column // above, or the more common symbolic alternatives. // // Additional criteria which are specific to other conditional format types are // shown in the relevant sections below. // // value: The value is generally used along with the criteria parameter to set // the rule by which the cell data will be evaluated: // // err := f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: ">", // Format: &format, // Value: "6", // }, // }, // ) // // The value property can also be an cell reference: // // err := f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: ">", // Format: &format, // Value: "$C$1", // }, // }, // ) // // type: format - The format parameter is used to specify the format that will // be applied to the cell when the conditional formatting criterion is met. The // format is created using the NewConditionalStyle function in the same way as // cell formats: // // format, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "9A0511"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1, // }, // }, // ) // if err != nil { // fmt.Println(err) // } // err = f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // {Type: "cell", Criteria: ">", Format: &format, Value: "6"}, // }, // ) // // Note: In Excel, a conditional format is superimposed over the existing cell // format and not all cell format properties can be modified. Properties that // cannot be modified in a conditional format are font name, font size, // superscript and subscript, diagonal borders, all alignment properties and all // protection properties. // // Excel specifies some default formats to be used with conditional formatting. // These can be replicated using the following excelize formats: // // // Rose format for bad conditional. // format1, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "9A0511"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1, // }, // }, // ) // // // Light yellow format for neutral conditional. // format2, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "9B5713"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"FEEAA0"}, Pattern: 1, // }, // }, // ) // // // Light green format for good conditional. // format3, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "09600B"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1, // }, // }, // ) // // type: MinValue - The 'MinValue' parameter is used to set the lower limiting // value when the criteria is either "between" or "not between". // // // Highlight cells rules: between... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: "between", // Format: &format, // MinValue: 6", // MaxValue: 8", // }, // }, // ) // // type: MaxValue - The 'MaxValue' parameter is used to set the upper limiting // value when the criteria is either "between" or "not between". See the // previous example. // // type: average - The average type is used to specify Excel's "Average" style // conditional format: // // // Top/Bottom rules: Above Average... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "average", // Criteria: "=", // Format: &format1, // AboveAverage: true, // }, // }, // ) // // // Top/Bottom rules: Below Average... // err := f.SetConditionalFormat("Sheet1", "B1:B10", // []excelize.ConditionalFormatOptions{ // { // Type: "average", // Criteria: "=", // Format: &format2, // AboveAverage: false, // }, // }, // ) // // type: duplicate - The duplicate type is used to highlight duplicate cells in // a range: // // // Highlight cells rules: Duplicate Values... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // {Type: "duplicate", Criteria: "=", Format: &format}, // }, // ) // // type: unique - The unique type is used to highlight unique cells in a range: // // // Highlight cells rules: Not Equal To... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // {Type: "unique", Criteria: "=", Format: &format}, // }, // ) // // type: top - The top type is used to specify the top n values by number or // percentage in a range: // // // Top/Bottom rules: Top 10. // err := f.SetConditionalFormat("Sheet1", "H1:H10", // []excelize.ConditionalFormatOptions{ // { // Type: "top", // Criteria: "=", // Format: &format, // Value: "6", // }, // }, // ) // // The criteria can be used to indicate that a percentage condition is required: // // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "top", // Criteria: "=", // Format: &format, // Value: "6", // Percent: true, // }, // }, // ) // // type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2 // Color Scale" style conditional format: // // // Color scales: 2 color. // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "2_color_scale", // Criteria: "=", // MinType: "min", // MaxType: "max", // MinColor: "#F8696B", // MaxColor: "#63BE7B", // }, // }, // ) // // This conditional type can be modified with MinType, MaxType, MinValue, // MaxValue, MinColor and MaxColor, see below. // // type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3 // Color Scale" style conditional format: // // // Color scales: 3 color. // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "3_color_scale", // Criteria: "=", // MinType: "min", // MidType: "percentile", // MaxType: "max", // MinColor: "#F8696B", // MidColor: "#FFEB84", // MaxColor: "#63BE7B", // }, // }, // ) // // This conditional type can be modified with MinType, MidType, MaxType, // MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see // below. // // type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // style conditional format. // // MinType - The MinType and MaxType properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // The MidType is available for 3_color_scale. The properties are used as // follows: // // // Data Bars: Gradient Fill. // err := f.SetConditionalFormat("Sheet1", "K1:K10", // []excelize.ConditionalFormatOptions{ // { // Type: "data_bar", // Criteria: "=", // MinType: "min", // MaxType: "max", // BarColor: "#638EC6", // }, // }, // ) // // The available min/mid/max types are: // // min (for MinType only) // num // percent // percentile // formula // max (for MaxType only) // // MidType - Used for 3_color_scale. Same as MinType, see above. // // MaxType - Same as MinType, see above. // // MinValue - The MinValue and MaxValue properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // // MidValue - The MidValue is available for 3_color_scale. Same as MinValue, // see above. // // MaxValue - Same as MinValue, see above. // // MinColor - The MinColor and MaxColor properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // // MidColor - The MidColor is available for 3_color_scale. The properties // are used as follows: // // // Color scales: 3 color. // err := f.SetConditionalFormat("Sheet1", "B1:B10", // []excelize.ConditionalFormatOptions{ // { // Type: "3_color_scale", // Criteria: "=", // MinType: "min", // MidType: "percentile", // MaxType: "max", // MinColor: "#F8696B", // MidColor: "#FFEB84", // MaxColor: "#63BE7B", // }, // }, // ) // // MaxColor - Same as MinColor, see above. // // BarColor - Used for data_bar. Same as MinColor, see above. // // BarBorderColor - Used for sets the color for the border line of a data bar, // this is only visible in Excel 2010 and later. // // BarDirection - sets the direction for data bars. The available options are: // // context - Data bar direction is set by spreadsheet application based on the context of the data displayed. // leftToRight - Data bar direction is from right to left. // rightToLeft - Data bar direction is from left to right. // // BarOnly - Used for set displays a bar data but not the data in the cells. // // BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this // is only visible in Excel 2010 and later. // // IconStyle - The available options are: // // 3Arrows // 3ArrowsGray // 3Flags // 3Signs // 3Stars // 3Symbols // 3Symbols2 // 3TrafficLights1 // 3TrafficLights2 // 3Triangles // 4Arrows // 4ArrowsGray // 4Rating // 4RedToBlack // 4TrafficLights // 5Arrows // 5ArrowsGray // 5Boxes // 5Quarters // 5Rating // // ReverseIcons - Used for set reversed icons sets. // // IconsOnly - Used for set displayed without the cell value. // // StopIfTrue - used to set the "stop if true" feature of a conditional // formatting rule when more than one rule is applied to a cell or a range of // cells. When this parameter is set then subsequent rules are not evaluated // if the current rule is true. func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFormatOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } SQRef, mastCell, err := prepareConditionalFormatRange(rangeRef) if err != nil { return err } // Create a pseudo GUID for each unique rule. var rules int for _, cf := range ws.ConditionalFormatting { rules += len(cf.CfRule) } var ( cfRule []*xlsxCfRule noCriteriaTypes = []string{ "containsBlanks", "notContainsBlanks", "containsErrors", "notContainsErrors", "expression", "iconSet", } ) for i, opt := range opts { var vt, ct string var ok bool // "type" is a required parameter, check for valid validation types. vt, ok = validType[opt.Type] if ok { // Check for valid criteria types. ct, ok = criteriaType[opt.Criteria] if ok || inStrSlice(noCriteriaTypes, vt, true) != -1 { drawFunc, ok := drawContFmtFunc[vt] if ok { priority := rules + i rule, x14rule := drawFunc(priority, ct, mastCell, fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), priority), &opt) if rule == nil && x14rule == nil { return ErrParameterInvalid } if rule != nil { cfRule = append(cfRule, rule) } if x14rule != nil { if err = f.appendCfRule(ws, x14rule, SQRef); err != nil { return err } f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) } continue } } return ErrParameterInvalid } return ErrParameterInvalid } if len(cfRule) > 0 { ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{ SQRef: SQRef, CfRule: cfRule, }) } return err } // prepareConditionalFormatRange returns checked cell range and master cell // reference by giving conditional formatting range reference. func prepareConditionalFormatRange(rangeRef string) (string, string, error) { var SQRef, mastCell string if rangeRef == "" { return SQRef, mastCell, ErrParameterRequired } rangeRef = strings.ReplaceAll(rangeRef, ",", " ") for i, cellRange := range strings.Split(rangeRef, " ") { var cellNames []string for j, ref := range strings.Split(cellRange, ":") { if j > 1 { return SQRef, mastCell, ErrParameterInvalid } cellRef, col, row, err := parseRef(ref) if err != nil { return SQRef, mastCell, err } var c, r int if col { if cellRef.Row = TotalRows; j == 0 { cellRef.Row = 1 } } if row { if cellRef.Col = MaxColumns; j == 0 { cellRef.Col = 1 } } c, r = cellRef.Col, cellRef.Row cellName, _ := CoordinatesToCellName(c, r) cellNames = append(cellNames, cellName) if i == 0 && j == 0 { mastCell = cellName } } SQRef += strings.Join(cellNames, ":") + " " } return strings.TrimSuffix(SQRef, " "), mastCell, nil } // appendCfRule provides a function to append rules to conditional formatting. func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule, sqref string) error { var ( err error idx int appendMode bool decodeExtLst = new(decodeExtLst) condFmts *xlsxX14ConditionalFormattings decodeCondFmts *decodeX14ConditionalFormattings ext *xlsxExt condFmtBytes, condFmtsBytes, extLstBytes []byte ) condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, Sqref: sqref, CfRule: []*xlsxX14CfRule{rule}}, }) if ws.ExtLst != nil { // append mode ext if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for idx, ext = range decodeExtLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts = new(decodeX14ConditionalFormattings) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) if condFmts == nil { condFmts = &xlsxX14ConditionalFormattings{} } condFmts.Content = decodeCondFmts.Content + string(condFmtBytes) condFmtsBytes, _ = xml.Marshal(condFmts) decodeExtLst.Ext[idx].Content = string(condFmtsBytes) appendMode = true } } } if !appendMode { condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes), }) } sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } // extractCondFmtCellIs provides a function to extract conditional format // settings for cell value (include between, not between, equal, not equal, // greater than and less than) by given conditional formatting rule. func (f *File) extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "cell", Criteria: operatorType[c.Operator]} if len(c.Formula) == 2 { format.MinValue, format.MaxValue = c.Formula[0], c.Formula[1] return format } format.Value = c.Formula[0] return format } // extractCondFmtTimePeriod provides a function to extract conditional format // settings for time period by given conditional formatting rule. func (f *File) extractCondFmtTimePeriod(c *xlsxCfRule, ref string, extLst *xlsxExtLst) ConditionalFormatOptions { var criteria string for _, formula := range c.Formula { if c, ok := map[string]string{ fmt.Sprintf("FLOOR(%s,1)=TODAY()-1", ref): "yesterday", fmt.Sprintf("FLOOR(%s,1)=TODAY()", ref): "today", fmt.Sprintf("FLOOR(%s,1)=TODAY()+1", ref): "tomorrow", fmt.Sprintf("AND(TODAY()-FLOOR(%[1]s,1)<=6,FLOOR(%[1]s,1)<=TODAY())", ref): "last 7 days", fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%[1]s,0)<(WEEKDAY(TODAY())+7))", ref): "last week", fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%[1]s,0)-TODAY()>=7-WEEKDAY(TODAY()))", ref): "this week", fmt.Sprintf("AND(ROUNDDOWN(%[1]s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%[1]s,0)-TODAY()<(15-WEEKDAY(TODAY())))", ref): "continue week", fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())-1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=1,YEAR(%[1]s)=YEAR(TODAY())-1)))", ref): "last month", fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY()),YEAR(%[1]s)=YEAR(TODAY()))", ref): "this month", fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())+1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=12,YEAR(%[1]s)=YEAR(TODAY())+1)))", ref): "continue month", }[formula]; ok { criteria = c break } } return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "time_period", Criteria: criteria} } // extractCondFmtText provides a function to extract conditional format // settings for text cell values by given conditional formatting rule. func (f *File) extractCondFmtText(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "text", Criteria: operatorType[c.Operator], Value: c.Text} } // extractCondFmtTop10 provides a function to extract conditional format // settings for top N (default is top 10) by given conditional formatting // rule. func (f *File) extractCondFmtTop10(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "top", Criteria: "=", Percent: c.Percent, Value: strconv.Itoa(c.Rank), } if c.Bottom { format.Type = "bottom" } return format } // extractCondFmtAboveAverage provides a function to extract conditional format // settings for above average and below average by given conditional formatting // rule. func (f *File) extractCondFmtAboveAverage(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "average", Criteria: "=", } if c.AboveAverage != nil { format.AboveAverage = *c.AboveAverage } return format } // extractCondFmtDuplicateUniqueValues provides a function to extract // conditional format settings for duplicate and unique values by given // conditional formatting rule. func (f *File) extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: map[string]string{ "duplicateValues": "duplicate", "uniqueValues": "unique", }[c.Type], Criteria: "=", } } // extractCondFmtBlanks provides a function to extract conditional format // settings for blank cells by given conditional formatting rule. func (f *File) extractCondFmtBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "blanks", } } // extractCondFmtNoBlanks provides a function to extract conditional format // settings for no blank cells by given conditional formatting rule. func (f *File) extractCondFmtNoBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "no_blanks", } } // extractCondFmtErrors provides a function to extract conditional format // settings for cells with errors by given conditional formatting rule. func (f *File) extractCondFmtErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "errors", } } // extractCondFmtNoErrors provides a function to extract conditional format // settings for cells without errors by given conditional formatting rule. func (f *File) extractCondFmtNoErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "no_errors", } } // extractCondFmtColorScale provides a function to extract conditional format // settings for color scale (include 2 color scale and 3 color scale) by given // conditional formatting rule. func (f *File) extractCondFmtColorScale(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue} format.Type, format.Criteria = "2_color_scale", "=" values := len(c.ColorScale.Cfvo) colors := len(c.ColorScale.Color) if colors > 1 && values > 1 { format.MinType = c.ColorScale.Cfvo[0].Type if c.ColorScale.Cfvo[0].Val != "0" { format.MinValue = c.ColorScale.Cfvo[0].Val } format.MinColor = "#" + f.getThemeColor(c.ColorScale.Color[0]) format.MaxType = c.ColorScale.Cfvo[1].Type if c.ColorScale.Cfvo[1].Val != "0" { format.MaxValue = c.ColorScale.Cfvo[1].Val } format.MaxColor = "#" + f.getThemeColor(c.ColorScale.Color[1]) } if colors == 3 { format.Type = "3_color_scale" format.MidType = c.ColorScale.Cfvo[1].Type if c.ColorScale.Cfvo[1].Val != "0" { format.MidValue = c.ColorScale.Cfvo[1].Val } format.MidColor = "#" + f.getThemeColor(c.ColorScale.Color[1]) format.MaxType = c.ColorScale.Cfvo[2].Type if c.ColorScale.Cfvo[2].Val != "0" { format.MaxValue = c.ColorScale.Cfvo[2].Val } format.MaxColor = "#" + f.getThemeColor(c.ColorScale.Color[2]) } return format } // extractCondFmtDataBarRule provides a function to extract conditional format // settings for data bar by given conditional formatting rule extension list. func (f *File) extractCondFmtDataBarRule(ID string, format *ConditionalFormatOptions, condFmts []decodeX14ConditionalFormatting) { for _, condFmt := range condFmts { for _, rule := range condFmt.CfRule { if rule.DataBar != nil && rule.ID == ID { format.BarDirection = rule.DataBar.Direction if rule.DataBar.Gradient != nil && !*rule.DataBar.Gradient { format.BarSolid = true } if rule.DataBar.BorderColor != nil { format.BarBorderColor = "#" + f.getThemeColor(rule.DataBar.BorderColor) } } } } } // extractCondFmtDataBar provides a function to extract conditional format // settings for data bar by given conditional formatting rule. func (f *File) extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="} if c.DataBar != nil { format.StopIfTrue = c.StopIfTrue format.MinType = c.DataBar.Cfvo[0].Type format.MinValue = c.DataBar.Cfvo[0].Val format.MaxType = c.DataBar.Cfvo[1].Type format.MaxValue = c.DataBar.Cfvo[1].Val format.BarColor = "#" + f.getThemeColor(c.DataBar.Color[0]) if c.DataBar.ShowValue != nil { format.BarOnly = !*c.DataBar.ShowValue } } extractExtLst := func(ID string, extLst *decodeExtLst) { for _, ext := range extLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts := new(decodeX14ConditionalFormattingRules) if err := xml.Unmarshal([]byte(ext.Content), &decodeCondFmts); err == nil { f.extractCondFmtDataBarRule(ID, &format, decodeCondFmts.CondFmt) } } } } if c.ExtLst != nil { ext := decodeX14ConditionalFormattingExt{} if err := xml.Unmarshal([]byte(c.ExtLst.Ext), &ext); err == nil && extLst != nil { decodeExtLst := new(decodeExtLst) if err = xml.Unmarshal([]byte(""+extLst.Ext+""), decodeExtLst); err == nil { extractExtLst(ext.ID, decodeExtLst) } } } return format } // extractCondFmtExp provides a function to extract conditional format settings // for expression by given conditional formatting rule. func (f *File) extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "formula"} if len(c.Formula) > 0 { format.Criteria = c.Formula[0] } return format } // extractCondFmtIconSet provides a function to extract conditional format // settings for icon sets by given conditional formatting rule. func (f *File) extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Type: "icon_set"} if c.IconSet != nil { if c.IconSet.ShowValue != nil { format.IconsOnly = !*c.IconSet.ShowValue } format.IconStyle = c.IconSet.IconSet format.ReverseIcons = c.IconSet.Reverse } return format } // extractX14CondFmtIconSet provides a function to extract conditional format // settings for icon sets by given conditional formatting rule. func (f *File) extractX14CondFmtIconSetRule(c *decodeX14CfRule) ConditionalFormatOptions { format := ConditionalFormatOptions{Type: "icon_set"} if c.IconSet != nil { if c.IconSet.ShowValue != nil { format.IconsOnly = !*c.IconSet.ShowValue } format.IconStyle = c.IconSet.IconSet format.ReverseIcons = c.IconSet.Reverse } return format } // GetConditionalFormats returns conditional format settings by given worksheet // name. func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) { conditionalFormats := make(map[string][]ConditionalFormatOptions) ws, err := f.workSheetReader(sheet) if err != nil { return conditionalFormats, err } for _, cf := range ws.ConditionalFormatting { var opts []ConditionalFormatOptions _, mastCell, err := prepareConditionalFormatRange(cf.SQRef) if err != nil { return conditionalFormats, err } for _, cr := range cf.CfRule { if extractFunc, ok := extractContFmtFunc[cr.Type]; ok { opts = append(opts, extractFunc(f, mastCell, cr, ws.ExtLst)) } } conditionalFormats[cf.SQRef] = opts } if ws.ExtLst != nil { decodeExtLst := new(decodeExtLst) if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return conditionalFormats, err } for _, ext := range decodeExtLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts := new(decodeX14ConditionalFormattingRules) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) for _, condFmt := range decodeCondFmts.CondFmt { var opts []ConditionalFormatOptions for _, rule := range condFmt.CfRule { if rule.Type == "iconSet" { opts = append(opts, f.extractX14CondFmtIconSetRule(rule)) } } conditionalFormats[condFmt.Sqref] = append(conditionalFormats[condFmt.Sqref], opts...) } } } } return conditionalFormats, err } // UnsetConditionalFormat provides a function to unset the conditional format // by given worksheet name and range reference. func (f *File) UnsetConditionalFormat(sheet, rangeRef string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } delCells, err := flatSqref(rangeRef) if err != nil { return err } for i := 0; i < len(ws.ConditionalFormatting); i++ { if ws.ConditionalFormatting[i].SQRef, err = deleteCellsFromSqref(ws.ConditionalFormatting[i].SQRef, delCells); err != nil { return err } if len(ws.ConditionalFormatting[i].SQRef) == 0 { ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...) i-- } } if ws.ExtLst != nil { var condFmtsBytes, extLstBytes []byte decodeExtLst := new(decodeExtLst) if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for idx := 0; idx < len(decodeExtLst.Ext); idx++ { ext := decodeExtLst.Ext[idx] if ext.URI == ExtURIConditionalFormattings { decodeCondFmts := new(decodeX14ConditionalFormattingRules) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) if condFmtsBytes, err = decodeCondFmts.deleteCfRule(delCells); err != nil { return err } decodeExtLst.Ext[idx].Content = string(condFmtsBytes) if len(decodeExtLst.Ext[idx].Content) == 57 { // empty x14:conditionalFormattings element decodeExtLst.Ext = append(decodeExtLst.Ext[:idx], decodeExtLst.Ext[idx+1:]...) idx-- } } } sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} if len(ws.ExtLst.Ext) == 0 { ws.ExtLst = nil } } return err } // deleteCfRule provides a function to delete conditional formatting rule by // given range reference and return the updated x14:conditionalFormattings // element content. func (r *decodeX14ConditionalFormattingRules) deleteCfRule(delCells map[int][][]int) ([]byte, error) { var ( err error condFmts = &xlsxX14ConditionalFormattings{} ) for _, condFmt := range r.CondFmt { if condFmt.Sqref, err = deleteCellsFromSqref(condFmt.Sqref, delCells); err != nil { return nil, err } if len(condFmt.Sqref) == 0 { continue } x14ConditionalFormatting := xlsxX14ConditionalFormatting{ XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, Pivot: condFmt.Pivot, Sqref: condFmt.Sqref, ExtLst: condFmt.ExtLst, } for _, rule := range condFmt.CfRule { x14CfRule := &xlsxX14CfRule{ Type: rule.Type, Priority: rule.Priority, StopIfTrue: rule.StopIfTrue, AboveAverage: rule.AboveAverage, Percent: rule.Percent, Bottom: rule.Bottom, Operator: rule.Operator, Text: rule.Text, TimePeriod: rule.TimePeriod, Rank: rule.Rank, StdDev: rule.StdDev, EqualAverage: rule.EqualAverage, ActivePresent: rule.ActivePresent, ID: rule.ID, F: rule.F, ColorScale: rule.ColorScale, Dxf: rule.Dxf, ExtLst: rule.ExtLst, } if rule.DataBar != nil { x14DataBar := &xlsx14DataBar{ MaxLength: rule.DataBar.MaxLength, MinLength: rule.DataBar.MinLength, Border: rule.DataBar.Border, ShowValue: rule.DataBar.ShowValue, Direction: rule.DataBar.Direction, BorderColor: rule.DataBar.BorderColor, NegativeFillColor: rule.DataBar.NegativeFillColor, AxisColor: rule.DataBar.AxisColor, } if rule.DataBar.Gradient != nil { x14DataBar.Gradient = *rule.DataBar.Gradient } if rule.DataBar.Cfvo != nil { for _, cfvo := range rule.DataBar.Cfvo { x14DataBar.Cfvo = append(x14DataBar.Cfvo, &xlsxCfvo{ Gte: cfvo.Gte, Type: cfvo.Type, Val: cfvo.Val, ExtLst: cfvo.ExtLst, }) } } x14CfRule.DataBar = x14DataBar } if rule.IconSet != nil { x14IconSet := &xlsx14IconSet{ IconSet: rule.IconSet.IconSet, ShowValue: rule.IconSet.ShowValue, Percent: rule.IconSet.Percent, Reverse: rule.IconSet.Reverse, Custom: rule.IconSet.Custom, CfIcon: rule.IconSet.CfIcon, } if rule.IconSet.Cfvo != nil { for _, cfvo := range rule.IconSet.Cfvo { x14IconSet.Cfvo = append(x14IconSet.Cfvo, &xlsx14Cfvo{ Type: cfvo.Type, Gte: cfvo.Gte, F: cfvo.F, ExtLst: cfvo.ExtLst, }) } } x14CfRule.IconSet = x14IconSet } x14ConditionalFormatting.CfRule = append(x14ConditionalFormatting.CfRule, x14CfRule) } condFmtBytes, _ := xml.Marshal(x14ConditionalFormatting) condFmts.Content += string(condFmtBytes) } return xml.Marshal(condFmts) } // drawCondFmtCellIs provides a function to create conditional formatting rule // for cell value (include between, not between, equal, not equal, greater // than and less than) by given priority, criteria type and format settings. func drawCondFmtCellIs(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Operator: ct, DxfID: format.Format, } // "between" and "not between" criteria require 2 values. if ct == "between" || ct == "notBetween" { c.Formula = append(c.Formula, []string{format.MinValue, format.MaxValue}...) } if inStrSlice(cellIsCriteriaType, ct, true) != -1 { c.Formula = append(c.Formula, format.Value) } return c, nil } // drawCondFmtTimePeriod provides a function to create conditional formatting // rule for time period by given priority, criteria type and format settings. func drawCondFmtTimePeriod(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: "timePeriod", Formula: []string{ map[string]string{ "yesterday": fmt.Sprintf("FLOOR(%s,1)=TODAY()-1", ref), "today": fmt.Sprintf("FLOOR(%s,1)=TODAY()", ref), "tomorrow": fmt.Sprintf("FLOOR(%s,1)=TODAY()+1", ref), "last7Days": fmt.Sprintf("AND(TODAY()-FLOOR(%[1]s,1)<=6,FLOOR(%[1]s,1)<=TODAY())", ref), "lastWeek": fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%[1]s,0)<(WEEKDAY(TODAY())+7))", ref), "thisWeek": fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%[1]s,0)-TODAY()>=7-WEEKDAY(TODAY()))", ref), "nextWeek": fmt.Sprintf("AND(ROUNDDOWN(%[1]s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%[1]s,0)-TODAY()<(15-WEEKDAY(TODAY())))", ref), "lastMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())-1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=1,YEAR(%[1]s)=YEAR(TODAY())-1)))", ref), "thisMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY()),YEAR(%[1]s)=YEAR(TODAY()))", ref), "nextMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())+1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=12,YEAR(%[1]s)=YEAR(TODAY())+1)))", ref), }[ct], }, DxfID: format.Format, }, nil } // drawCondFmtText provides a function to create conditional formatting rule for // text cell values by given priority, criteria type and format settings. func drawCondFmtText(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: map[string]string{ "containsText": "containsText", "notContains": "notContainsText", "beginsWith": "beginsWith", "endsWith": "endsWith", }[ct], Text: format.Value, Operator: ct, Formula: []string{ map[string]string{ "containsText": fmt.Sprintf("NOT(ISERROR(SEARCH(\"%s\",%s)))", strings.NewReplacer(`"`, `""`).Replace(format.Value), ref), "notContains": fmt.Sprintf("ISERROR(SEARCH(\"%s\",%s))", strings.NewReplacer(`"`, `""`).Replace(format.Value), ref), "beginsWith": fmt.Sprintf("LEFT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"", strings.NewReplacer(`"`, `""`).Replace(format.Value), ref), "endsWith": fmt.Sprintf("RIGHT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"", strings.NewReplacer(`"`, `""`).Replace(format.Value), ref), }[ct], }, DxfID: format.Format, }, nil } // drawCondFmtTop10 provides a function to create conditional formatting rule // for top N (default is top 10) by given priority, criteria type and format // settings. func drawCondFmtTop10(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Bottom: format.Type == "bottom", Type: validType[format.Type], Rank: 10, DxfID: format.Format, Percent: format.Percent, } if rank, err := strconv.Atoi(format.Value); err == nil { c.Rank = rank } return c, nil } // drawCondFmtAboveAverage provides a function to create conditional // formatting rule for above average and below average by given priority, // criteria type and format settings. func drawCondFmtAboveAverage(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], AboveAverage: boolPtr(format.AboveAverage), DxfID: format.Format, }, nil } // drawCondFmtDuplicateUniqueValues provides a function to create conditional // formatting rule for duplicate and unique values by given priority, criteria // type and format settings. func drawCondFmtDuplicateUniqueValues(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], DxfID: format.Format, }, nil } // drawCondFmtColorScale provides a function to create conditional formatting // rule for color scale (include 2 color scale and 3 color scale) by given // priority, criteria type and format settings. func drawCondFmtColorScale(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { minValue := format.MinValue if minValue == "" { minValue = "0" } maxValue := format.MaxValue if maxValue == "" { maxValue = "0" } midValue := format.MidValue if midValue == "" { midValue = "50" } c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: "colorScale", ColorScale: &xlsxColorScale{ Cfvo: []*xlsxCfvo{ {Type: format.MinType, Val: minValue}, }, Color: []*xlsxColor{ {RGB: getPaletteColor(format.MinColor)}, }, }, } if validType[format.Type] == "3_color_scale" { c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MidType, Val: midValue}) c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MidColor)}) } c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MaxType, Val: maxValue}) c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MaxColor)}) return c, nil } // drawCondFmtDataBar provides a function to create conditional formatting // rule for data bar by given priority, criteria type and format settings. func drawCondFmtDataBar(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { var x14CfRule *xlsxX14CfRule var extLst *xlsxExtLst if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" { extLst = &xlsxExtLst{Ext: fmt.Sprintf(`%s`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)} x14CfRule = &xlsxX14CfRule{ Type: validType[format.Type], ID: GUID, DataBar: &xlsx14DataBar{ MaxLength: 100, Border: format.BarBorderColor != "", Gradient: !format.BarSolid, Direction: format.BarDirection, Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}}, NegativeFillColor: &xlsxColor{RGB: "FFFF0000"}, AxisColor: &xlsxColor{RGB: "FFFF0000"}, }, } if x14CfRule.DataBar.Border { x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)} } } return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], DataBar: &xlsxDataBar{ ShowValue: boolPtr(!format.BarOnly), Cfvo: []*xlsxCfvo{{Type: format.MinType, Val: format.MinValue}, {Type: format.MaxType, Val: format.MaxValue}}, Color: []*xlsxColor{{RGB: getPaletteColor(format.BarColor)}}, }, ExtLst: extLst, }, x14CfRule } // drawCondFmtExp provides a function to create conditional formatting rule // for expression by given priority, criteria type and format settings. func drawCondFmtExp(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{format.Criteria}, DxfID: format.Format, }, nil } // drawCondFmtErrors provides a function to create conditional formatting rule // for cells with errors by given priority, criteria type and format settings. func drawCondFmtErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{fmt.Sprintf("ISERROR(%s)", ref)}, DxfID: format.Format, }, nil } // drawCondFmtNoErrors provides a function to create conditional formatting rule // for cells without errors by given priority, criteria type and format settings. func drawCondFmtNoErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{fmt.Sprintf("NOT(ISERROR(%s))", ref)}, DxfID: format.Format, }, nil } // drawCondFmtBlanks provides a function to create conditional formatting rule // for blank cells by given priority, criteria type and format settings. func drawCondFmtBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{fmt.Sprintf("LEN(TRIM(%s))=0", ref)}, DxfID: format.Format, }, nil } // drawCondFmtNoBlanks provides a function to create conditional formatting rule // for no blanks cells by given priority, criteria type and format settings. func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{fmt.Sprintf("LEN(TRIM(%s))>0", ref)}, DxfID: format.Format, }, nil } // drawCondFmtIconSet provides a function to create conditional formatting rule // for icon set by given priority, criteria type and format settings. func drawCondFmtIconSet(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { if cfRule, ok := condFmtIconSetPresets[format.IconStyle]; ok { cfRule.Priority = p + 1 cfRule.IconSet.IconSet = format.IconStyle cfRule.IconSet.Reverse = format.ReverseIcons cfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly) cfRule.Type = validType[format.Type] return cfRule, nil } if x14CfRule, ok := condFmtX14IconSetPresets[format.IconStyle]; ok { x14CfRule.Type = validType[format.Type] x14CfRule.Priority = p + 1 x14CfRule.StopIfTrue = format.StopIfTrue x14CfRule.AboveAverage = boolPtr(format.AboveAverage) x14CfRule.Percent = format.Percent x14CfRule.ID = GUID x14CfRule.IconSet.IconSet = format.IconStyle x14CfRule.IconSet.Reverse = format.ReverseIcons x14CfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly) return nil, x14CfRule } return nil, nil } // getPaletteColor provides a function to convert the RBG color by given // string. func getPaletteColor(color string) string { return "FF" + strings.ReplaceAll(strings.ToUpper(color), "#", "") } // themeReader provides a function to get the pointer to the xl/theme/theme1.xml // structure after deserialization. func (f *File) themeReader() (*decodeTheme, error) { if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok { return nil, nil } theme := decodeTheme{} if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))). Decode(&theme); err != nil && err != io.EOF { return &theme, err } return &theme, nil } // ThemeColor applied the color with tint value. func ThemeColor(baseColor string, tint float64) string { if tint == 0 { return "FF" + baseColor } r, _ := strconv.ParseUint(baseColor[:2], 16, 64) g, _ := strconv.ParseUint(baseColor[2:4], 16, 64) b, _ := strconv.ParseUint(baseColor[4:6], 16, 64) var h, s, l float64 if r <= math.MaxUint8 && g <= math.MaxUint8 && b <= math.MaxUint8 { h, s, l = RGBToHSL(uint8(r), uint8(g), uint8(b)) } if tint < 0 { l *= 1 + tint } else { l = l*(1-tint) + (1 - (1 - tint)) } br, bg, bb := HSLToRGB(h, s, l) return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb) } ================================================ FILE: styles_test.go ================================================ package excelize import ( "fmt" "math" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestStyleFill(t *testing.T) { cases := []struct { label string format *Style expectFill bool }{{ label: "no_fill", format: &Style{Alignment: &Alignment{WrapText: true}}, expectFill: false, }, { label: "fill", format: &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}}, expectFill: true, }} for _, testCase := range cases { xl := NewFile() styleID, err := xl.NewStyle(testCase.format) assert.NoError(t, err) styles, err := xl.stylesReader() assert.NoError(t, err) style := styles.CellXfs.Xf[styleID] if testCase.expectFill { assert.NotEqual(t, *style.FillID, 0, testCase.label) } else { assert.Equal(t, *style.FillID, 0, testCase.label) } } f := NewFile() styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}}) assert.NoError(t, err) styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}}) assert.NoError(t, err) assert.Equal(t, styleID1, styleID2) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx"))) } func TestSetConditionalFormat(t *testing.T) { cases := []struct { label string format []ConditionalFormatOptions rules []*xlsxCfRule }{{ label: "3_color_scale", format: []ConditionalFormatOptions{{ Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "0", MaxValue: "10", MinColor: "ff0000", MidColor: "00ff00", MaxColor: "0000ff", }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", ColorScale: &xlsxColorScale{ Cfvo: []*xlsxCfvo{{ Type: "num", Val: "-10", }, { Type: "num", Val: "0", }, { Type: "num", Val: "10", }}, Color: []*xlsxColor{{ RGB: "FFFF0000", }, { RGB: "FF00FF00", }, { RGB: "FF0000FF", }}, }, }}, }, { label: "3_color_scale default min/mid/max", format: []ConditionalFormatOptions{{ Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinColor: "ff0000", MidColor: "00ff00", MaxColor: "0000ff", }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", ColorScale: &xlsxColorScale{ Cfvo: []*xlsxCfvo{{ Type: "num", Val: "0", }, { Type: "num", Val: "50", }, { Type: "num", Val: "0", }}, Color: []*xlsxColor{{ RGB: "FFFF0000", }, { RGB: "FF00FF00", }, { RGB: "FF0000FF", }}, }, }}, }, { label: "2_color_scale default min/max", format: []ConditionalFormatOptions{{ Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "ff0000", MaxColor: "0000ff", }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", ColorScale: &xlsxColorScale{ Cfvo: []*xlsxCfvo{{ Type: "num", Val: "0", }, { Type: "num", Val: "0", }}, Color: []*xlsxColor{{ RGB: "FFFF0000", }, { RGB: "FF0000FF", }}, }, }}, }} for _, testCase := range cases { f := NewFile() const sheet = "Sheet1" const rangeRef = "A1:A1" assert.NoError(t, f.SetConditionalFormat(sheet, rangeRef, testCase.format)) ws, err := f.workSheetReader(sheet) assert.NoError(t, err) cf := ws.ConditionalFormatting assert.Len(t, cf, 1, testCase.label) assert.Len(t, cf[0].CfRule, 1, testCase.label) assert.Equal(t, rangeRef, cf[0].SQRef, testCase.label) assert.EqualValues(t, testCase.rules, cf[0].CfRule, testCase.label) } // Test creating a conditional format with a solid color data bar style f := NewFile() condFmts := []ConditionalFormatOptions{ {Type: "data_bar", BarColor: "#A9D08E", BarSolid: true, Format: intPtr(0), Criteria: "=", MinType: "min", MaxType: "max"}, } for _, ref := range []string{"A1:A2", "B1:B2"} { assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts)) } f = NewFile() // Test creating a conditional format without cell reference assert.Equal(t, ErrParameterRequired, f.SetConditionalFormat("Sheet1", "", nil)) // Test creating a conditional format with invalid cell reference assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2:A3", nil)) // Test creating a conditional format with existing extension lists ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURISlicerListX14, ExtURISparklineGroups)} assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}})) f = NewFile() // Test creating a conditional format with invalid extension list characters ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: ""} assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element closed by ") // Test creating a conditional format with invalid icon set style assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "unknown"}})) // Test unsupported conditional formatting rule types assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: "unsupported"}})) t.Run("multi_conditional_formatting_rules_priority", func(t *testing.T) { f := NewFile() var condFmts []ConditionalFormatOptions for _, color := range []string{ "#264B96", // Blue "#F9A73E", // Yellow "#006F3C", // Green } { condFmts = append(condFmts, ConditionalFormatOptions{ Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "0", MaxValue: "5", BarColor: color, BarSolid: true, }) } assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A5", condFmts)) assert.NoError(t, f.SetConditionalFormat("Sheet1", "B1:B5", condFmts)) for r := 1; r <= 20; r++ { cell, err := CoordinatesToCellName(1, r) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", cell, r)) cell, err = CoordinatesToCellName(2, r) assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", cell, r)) } ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) var priorities []int expected := []int{1, 2, 3, 4, 5, 6} for _, condFmt := range ws.(*xlsxWorksheet).ConditionalFormatting { for _, rule := range condFmt.CfRule { priorities = append(priorities, rule.Priority) } } assert.Equal(t, expected, priorities) assert.NoError(t, f.Close()) }) } func TestGetConditionalFormats(t *testing.T) { f := NewFile() idx, err := f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) for idx, format := range [][]ConditionalFormatOptions{ {{Type: "cell", Format: &idx, Criteria: "greater than", Value: "6"}}, {{Type: "cell", Format: &idx, Criteria: "between", MinValue: "6", MaxValue: "8"}}, {{Type: "time_period", Format: &idx, Criteria: "yesterday"}}, {{Type: "time_period", Format: &idx, Criteria: "today"}}, {{Type: "time_period", Format: &idx, Criteria: "tomorrow"}}, {{Type: "time_period", Format: &idx, Criteria: "last 7 days"}}, {{Type: "time_period", Format: &idx, Criteria: "last week"}}, {{Type: "time_period", Format: &idx, Criteria: "this week"}}, {{Type: "time_period", Format: &idx, Criteria: "continue week"}}, {{Type: "time_period", Format: &idx, Criteria: "last month"}}, {{Type: "time_period", Format: &idx, Criteria: "this month"}}, {{Type: "time_period", Format: &idx, Criteria: "continue month"}}, {{Type: "text", Format: &idx, Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}}, {{Type: "text", Format: &idx, Criteria: "not containing", Value: "text"}}, {{Type: "text", Format: &idx, Criteria: "begins with", Value: "prefix"}}, {{Type: "text", Format: &idx, Criteria: "ends with", Value: "suffix"}}, {{Type: "top", Format: &idx, Criteria: "=", Value: "6"}}, {{Type: "bottom", Format: &idx, Criteria: "=", Value: "6"}}, {{Type: "average", AboveAverage: true, Format: &idx, Criteria: "="}}, {{Type: "duplicate", Format: &idx, Criteria: "="}}, {{Type: "unique", Format: &idx, Criteria: "="}}, {{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}}, {{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}}, {{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}}, {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}}, {{Type: "formula", Format: &idx, Criteria: "1"}}, {{Type: "blanks", Format: &idx}}, {{Type: "no_blanks", Format: &idx}}, {{Type: "errors", Format: &idx}}, {{Type: "no_errors", Format: &idx}}, {{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}}, {{Type: "icon_set", IconStyle: "3Stars", ReverseIcons: true, IconsOnly: true}}, {{Type: "icon_set", IconStyle: "3Triangles", ReverseIcons: true, IconsOnly: true}}, {{Type: "icon_set", IconStyle: "5Boxes", ReverseIcons: true, IconsOnly: true}}, } { col, err := ColumnNumberToName(idx + 2) assert.NoError(t, err) err = f.SetConditionalFormat("Sheet1", fmt.Sprintf("%s1:%s10", col, col), format) assert.NoError(t, err) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Equal(t, format, opts[fmt.Sprintf("%s1:%s10", col, col)]) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetConditionalFormats.xlsx"))) // Test unset all conditional formats f, err = OpenFile(filepath.Join("test", "TestGetConditionalFormats.xlsx")) assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"C1"}, Range: []string{"Sheet1!A1:B1"}, })) for _, rangeRef := range []string{"Y1:Y10", "Z1:Z10", "AG1:AG10", "AH1:AH10", "AI1:AI10"} { assert.NoError(t, f.UnsetConditionalFormat("Sheet1", rangeRef)) } assert.NoError(t, err) assert.NoError(t, f.Close()) // Test unset conditional formats with invalid extension list characters f, err = OpenFile(filepath.Join("test", "TestGetConditionalFormats.xlsx")) assert.NoError(t, err) w, err := f.workSheetReader("Sheet1") assert.NoError(t, err) w.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIConditionalFormattings)} assert.EqualError(t, f.UnsetConditionalFormat("Sheet1", "Y1:Y10"), "XML syntax error on line 1: element closed by ") assert.NoError(t, f.Close()) // Test get multiple conditional formats f = NewFile() expected := []ConditionalFormatOptions{ {Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}, {Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: false, StopIfTrue: true}, } err = f.SetConditionalFormat("Sheet1", "A2:A1,B:B,2:2", expected) assert.NoError(t, err) opts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Equal(t, expected, opts["A2:A1 B1:B1048576 A2:XFD2"]) f = NewFile() // Test get conditional formats on no exists worksheet _, err = f.GetConditionalFormats("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test get conditional formats with invalid sheet name _, err = f.GetConditionalFormats("Sheet:1") assert.Equal(t, ErrSheetNameInvalid, err) // Test get conditional formats with invalid extension list characters ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) ws.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIConditionalFormattings)} _, err = f.GetConditionalFormats("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: element closed by ") } func TestUnsetConditionalFormat(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7)) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: &format, Value: "6"}})) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) // Test unset conditional format with invalid range assert.Equal(t, f.UnsetConditionalFormat("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) // Test unset conditional format on not exists worksheet assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist") // Test unset conditional format with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.UnsetConditionalFormat("Sheet:1", "A1:A10")) // Test unset conditional format from extLst assert.NoError(t, f.SetConditionalFormat("Sheet1", "B1:B10", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "3Stars"}})) assert.NoError(t, f.SetConditionalFormat("Sheet1", "C1:C10", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "5Boxes"}})) condFmts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, condFmts, 2) // Unset the first extLst conditional format assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "B1:B10")) condFmts, err = f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, condFmts, 1) assert.NotNil(t, condFmts["C1:C10"]) // Unset the last extLst conditional format assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "C1:C10")) condFmts, err = f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, condFmts, 0) // Save spreadsheet by the given path assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnsetConditionalFormat.xlsx"))) // Test get and unset conditional format with invalid sqref value f = NewFile() ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) ws.ConditionalFormatting = []*xlsxConditionalFormatting{{SQRef: ""}} _, err = f.GetConditionalFormats("Sheet1") assert.Equal(t, ErrParameterRequired, err) ws.ConditionalFormatting = []*xlsxConditionalFormatting{{SQRef: "A"}} assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.UnsetConditionalFormat("Sheet1", "A1")) // Test unset conditional formats with invalid extension list characters ws.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(` A`, ExtURIConditionalFormattings)} assert.Equal(t, f.UnsetConditionalFormat("Sheet1", "A1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) t.Run("with_unordered_sqref", func(t *testing.T) { f := NewFile() condFmt := []ConditionalFormatOptions{{Type: "cell", Criteria: "greater than", Value: "6"}} assert.NoError(t, f.SetConditionalFormat("Sheet1", "A5:A10 A15:A20 A3:A4", condFmt)) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A7")) condFmts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, condFmts, 1) assert.Equal(t, condFmt, condFmts["A3:A6 A8:A10 A15:A20"]) }) t.Run("with_unordered_sqref_in_extLst", func(t *testing.T) { f := NewFile() condFmt := []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "3Stars"}} assert.NoError(t, f.SetConditionalFormat("Sheet1", "A5:A10 A15:A20 A3:A4", condFmt)) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A7")) condFmts, err := f.GetConditionalFormats("Sheet1") assert.NoError(t, err) assert.Len(t, condFmts, 1) assert.Equal(t, condFmt, condFmts["A3:A6 A8:A10 A15:A20"]) }) } func TestNewStyle(t *testing.T) { f := NewFile() for i := range 16 { _, err := f.NewStyle(&Style{ Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: i}, }) assert.NoError(t, err) } _, err := f.NewStyle(&Style{ Fill: Fill{Type: "solid"}, }) assert.Equal(t, ErrFillType, err) _, err = f.NewStyle(&Style{ Fill: Fill{Type: "gradient", Color: []string{"FFFFFF"}, Shading: 0}, }) assert.Equal(t, ErrFillGradientColor, err) _, err = f.NewStyle(&Style{ Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: 17}, }) assert.Equal(t, ErrFillGradientShading, err) _, err = f.NewStyle(&Style{ Fill: Fill{Type: "pattern", Color: []string{"FFFFFF", "4E71BE"}, Shading: 0}, }) assert.Equal(t, ErrFillPatternColor, err) _, err = f.NewStyle(&Style{ Fill: Fill{Type: "pattern", Pattern: 19}, }) assert.Equal(t, ErrFillPattern, err) f = NewFile() styleID, err := f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "777777"}}) assert.NoError(t, err) styles, err := f.stylesReader() assert.NoError(t, err) fontID := styles.CellXfs.Xf[styleID].FontID font := styles.Fonts.Font[*fontID] assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name") assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles") _, err = f.NewStyle(&Style{}) assert.NoError(t, err) _, err = f.NewStyle(nil) assert.NoError(t, err) // Test gradient fills f = NewFile() styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: 1, Pattern: 1}}) assert.NoError(t, err) styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FF0000", "4E71BE"}, Shading: 1, Pattern: 1}}) assert.NoError(t, err) assert.NotEqual(t, styleID1, styleID2) var exp string f = NewFile() _, err = f.NewStyle(&Style{CustomNumFmt: &exp}) assert.Equal(t, ErrCustomNumFmt, err) _, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}}) assert.Equal(t, ErrFontLength, err) _, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}}) assert.Equal(t, ErrFontSize, err) // Test create numeric custom style numFmt := "####;####" f.Styles.NumFmts = nil styleID, err = f.NewStyle(&Style{ CustomNumFmt: &numFmt, }) assert.NoError(t, err) assert.Equal(t, 1, styleID) assert.NotNil(t, f.Styles) assert.NotNil(t, f.Styles.CellXfs) assert.NotNil(t, f.Styles.CellXfs.Xf) nf := f.Styles.CellXfs.Xf[styleID] assert.Equal(t, 164, *nf.NumFmtID) // Test create currency custom style f.Styles.NumFmts = nil styleID, err = f.NewStyle(&Style{ NumFmt: 32, // must not be in currencyNumFmt }) assert.NoError(t, err) assert.Equal(t, 2, styleID) assert.NotNil(t, f.Styles) assert.NotNil(t, f.Styles.CellXfs) assert.NotNil(t, f.Styles.CellXfs.Xf) nf = f.Styles.CellXfs.Xf[styleID] assert.Equal(t, 32, *nf.NumFmtID) // Test set build-in scientific number format styleID, err = f.NewStyle(&Style{NumFmt: 11}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", styleID)) assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]float64{1.23, 1.234})) rows, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows) f = NewFile() // Test currency number format customNumFmt := "[$$-409]#,##0.00" style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) style2, err := f.NewStyle(&Style{NumFmt: 165}) assert.NoError(t, err) assert.Equal(t, style1, style2) style3, err := f.NewStyle(&Style{NumFmt: 166}) assert.NoError(t, err) assert.Equal(t, 2, style3) f = NewFile() f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = nil style4, err := f.NewStyle(&Style{NumFmt: 160}) assert.NoError(t, err) assert.Equal(t, 0, style4) f = NewFile() f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = nil style5, err := f.NewStyle(&Style{NumFmt: 160}) assert.NoError(t, err) assert.Equal(t, 0, style5) // Test create style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.NewStyle(&Style{NumFmt: 165}) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test create cell styles reach maximum f = NewFile() f.Styles.CellXfs.Xf = make([]xlsxXf, MaxCellStyles) f.Styles.CellXfs.Count = MaxCellStyles _, err = f.NewStyle(&Style{NumFmt: 0}) assert.Equal(t, ErrCellStyles, err) t.Run("for_create_new_style_with_font_charset", func(t *testing.T) { f, charset := NewFile(), 178 style := &Style{Font: &Font{ Family: "B Titr", Size: 12, Color: "000000", Charset: &charset, }} styleID, err := f.NewStyle(style) assert.NoError(t, err) assert.Equal(t, 1, styleID) s, err := f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, style.Font, s.Font) text := "\u0627\u06cc\u0646\u0020\u06cc\u06a9\u0020\u0645\u062a\u0646\u0020\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc\u0020\u0627\u0633\u062a\u002e" assert.NoError(t, f.SetCellValue("Sheet1", "A1", text)) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetFontCharset.xlsx"))) }) t.Run("for_recreate_default_style", func(t *testing.T) { f := NewFile() style, err := f.GetStyle(0) assert.NoError(t, err) styleID, err := f.NewStyle(style) assert.NoError(t, err) assert.Equal(t, 0, styleID) }) } func TestConditionalStyle(t *testing.T) { f := NewFile() expected := &Style{Protection: &Protection{Hidden: true, Locked: true}} idx, err := f.NewConditionalStyle(expected) assert.NoError(t, err) style, err := f.GetConditionalStyle(idx) assert.NoError(t, err) assert.Equal(t, expected, style) _, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(4), NumFmt: 165, NegRed: true}) assert.NoError(t, err) _, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(-1)}) assert.NoError(t, err) expected = &Style{NumFmt: 1} idx, err = f.NewConditionalStyle(expected) assert.NoError(t, err) style, err = f.GetConditionalStyle(idx) assert.NoError(t, err) assert.Equal(t, expected.NumFmt, style.NumFmt) assert.Zero(t, *style.DecimalPlaces) _, err = f.NewConditionalStyle(&Style{NumFmt: 27}) assert.NoError(t, err) numFmt := "general" _, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt}) assert.NoError(t, err) numFmt1 := "0.00" _, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt1}) assert.NoError(t, err) // Test create conditional style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get conditional style with invalid style index _, err = f.GetConditionalStyle(1) assert.Equal(t, newInvalidStyleID(1), err) // Test get conditional style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.GetConditionalStyle(1) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") f = NewFile() // Test get conditional style with background color and empty pattern type idx, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.PatternType = "" f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.FgColor = nil f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.BgColor = &xlsxColor{Theme: intPtr(6)} style, err = f.GetConditionalStyle(idx) assert.NoError(t, err) assert.Equal(t, "pattern", style.Fill.Type) assert.Equal(t, []string{"A5A5A5"}, style.Fill.Color) } func TestGetDefaultFont(t *testing.T) { f := NewFile() s, err := f.GetDefaultFont() assert.NoError(t, err) assert.Equal(t, s, "Calibri", "Default font should be Calibri") // Test get default font with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.GetDefaultFont() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetDefaultFont(t *testing.T) { f := NewFile() assert.NoError(t, f.SetDefaultFont("Arial")) styles, err := f.stylesReader() assert.NoError(t, err) s, err := f.GetDefaultFont() assert.NoError(t, err) assert.Equal(t, s, "Arial", "Default font should change to Arial") assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true) // Test set default font with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.SetDefaultFont("Arial"), "XML syntax error on line 1: invalid UTF-8") } func TestStylesReader(t *testing.T) { f := NewFile() // Test read styles with unsupported charset f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) styles, err := f.stylesReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.EqualValues(t, new(xlsxStyleSheet), styles) } func TestThemeReader(t *testing.T) { f := NewFile() // Test read theme with unsupported charset f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset) theme, err := f.themeReader() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.EqualValues(t, &decodeTheme{}, theme) } func TestSetCellStyle(t *testing.T) { f := NewFile() // Test set cell style on not exists worksheet assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN does not exist") // Test set cell style with invalid style ID assert.Equal(t, newInvalidStyleID(-1), f.SetCellStyle("Sheet1", "A1", "A2", -1)) // Test set cell style with not exists style ID assert.Equal(t, newInvalidStyleID(10), f.SetCellStyle("Sheet1", "A1", "A2", 10)) // Test set cell style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8") } func TestGetStyleID(t *testing.T) { f := NewFile() styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil) assert.NoError(t, err) assert.Equal(t, -1, styleID) } func TestGetFillID(t *testing.T) { styles, err := NewFile().stylesReader() assert.NoError(t, err) assert.Equal(t, -1, getFillID(styles, &Style{Fill: Fill{Type: "unknown"}})) } func TestThemeColor(t *testing.T) { for _, clr := range [][]string{ {"FF000000", ThemeColor("000000", -0.1)}, {"FF000000", ThemeColor("000000", 0)}, {"FF33FF33", ThemeColor("00FF00", 0.2)}, {"FFFFFFFF", ThemeColor("000000", 1)}, {"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(math.MaxUint8+1)), 6), 1)}, {"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(-1)), 6), 1)}, } { assert.Equal(t, clr[0], clr[1]) } } func TestGetNumFmtID(t *testing.T) { f := NewFile() fs1, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 10}) assert.NoError(t, err) id1 := getNumFmtID(&xlsxStyleSheet{}, fs1) fs2, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 0}) assert.NoError(t, err) id2 := getNumFmtID(&xlsxStyleSheet{}, fs2) assert.NotEqual(t, id1, id2) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleNumFmt.xlsx"))) } func TestGetThemeColor(t *testing.T) { assert.Empty(t, (&File{}).getThemeColor(&xlsxColor{})) f := NewFile() assert.Empty(t, f.getThemeColor(nil)) var theme int assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{Theme: &theme})) assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{RGB: "FFFFFF"})) assert.Equal(t, "FF8080", f.getThemeColor(&xlsxColor{Indexed: 2, Tint: 0.5})) assert.Empty(t, f.getThemeColor(&xlsxColor{Indexed: len(IndexedColorMapping), Tint: 0.5})) clr := &decodeCTColor{} assert.Nil(t, clr.colorChoice()) } func TestGetStyle(t *testing.T) { f := NewFile() expected := &Style{ Border: []Border{ {Type: "left", Color: "0000FF", Style: 3}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "top", Color: "00FF00", Style: 4}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "diagonalUp", Color: "A020F0", Style: 7}, {Type: "diagonalDown", Color: "A020F0", Style: 7}, }, Fill: Fill{Type: "gradient", Shading: 16, Color: []string{"0000FF", "00FF00"}}, Font: &Font{ Bold: true, Italic: true, Underline: "single", Family: "Arial", Size: 8.5, Strike: true, Color: "777777", ColorIndexed: 1, ColorTint: 0.1, VertAlign: "superscript", }, Alignment: &Alignment{ Horizontal: "center", Indent: 1, JustifyLastLine: true, ReadingOrder: 1, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 180, Vertical: "center", WrapText: true, }, Protection: &Protection{Hidden: true, Locked: true}, NumFmt: 49, } styleID, err := f.NewStyle(expected) assert.NoError(t, err) style, err := f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, expected.Border, style.Border) assert.Equal(t, expected.Fill, style.Fill) assert.Equal(t, expected.Font, style.Font) assert.Equal(t, expected.Alignment, style.Alignment) assert.Equal(t, expected.Protection, style.Protection) assert.Equal(t, expected.NumFmt, style.NumFmt) assert.Nil(t, style.DecimalPlaces) expected = &Style{ Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"0000FF"}}, } styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, expected.Fill, style.Fill) assert.Nil(t, style.DecimalPlaces) expected = &Style{NumFmt: 2} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, expected.NumFmt, style.NumFmt) assert.Equal(t, 2, *style.DecimalPlaces) expected = &Style{NumFmt: 27} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, expected.NumFmt, style.NumFmt) assert.Nil(t, style.DecimalPlaces) expected = &Style{NumFmt: 165} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, expected.NumFmt, style.NumFmt) assert.Equal(t, 2, *style.DecimalPlaces) decimal := 4 expected = &Style{NumFmt: 165, DecimalPlaces: &decimal, NegRed: true} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, 0, style.NumFmt) assert.Equal(t, *expected.DecimalPlaces, *style.DecimalPlaces) assert.Equal(t, "[$$-409]#,##0.0000;[Red][$$-409]#,##0.0000", *style.CustomNumFmt) for _, val := range [][]interface{}{ {"$#,##0", 0}, {"$#,##0.0", 1}, {"_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", 0}, {"_($* #,##000_);_($* (#,##000);_($* \"-\"_);_(@_)", 0}, {"_($* #,##0.0000_);_($* (#,##0.0000);_($* \"-\"????_);_(@_)", 4}, } { numFmtCode := val[0].(string) expected = &Style{CustomNumFmt: &numFmtCode} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, val[1].(int), *style.DecimalPlaces, numFmtCode) } for _, val := range []string{ ";$#,##0", ";$#,##0;", ";$#,##0.0", ";$#,##0.0;", "$#,##0;0.0", "_($* #,##0_);;_($* \"-\"_);_(@_)", "_($* #,##0.0_);_($* (#,##0.00);_($* \"-\"_);_(@_)", } { expected = &Style{CustomNumFmt: &val} styleID, err = f.NewStyle(expected) assert.NoError(t, err) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Nil(t, style.DecimalPlaces) } // Test get style with custom color index f.Styles.Colors = &xlsxStyleColors{ IndexedColors: &xlsxIndexedColors{ RgbColor: []xlsxColor{{RGB: "FF012345"}}, }, } assert.Equal(t, "012345", f.getThemeColor(&xlsxColor{Indexed: 0})) f.Styles.Fonts.Font[0].U = &attrValString{} f.Styles.CellXfs.Xf[0].FontID = intPtr(0) style, err = f.GetStyle(styleID) assert.NoError(t, err) assert.Equal(t, "single", style.Font.Underline) // Test get style with invalid style index style, err = f.GetStyle(-1) assert.Nil(t, style) assert.Equal(t, err, newInvalidStyleID(-1)) // Test get style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) style, err = f.GetStyle(1) assert.Nil(t, style) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } ================================================ FILE: table.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "regexp" "strconv" "strings" ) var ( expressionFormat = regexp.MustCompile(`"(?:[^"]|"")*"|\S+`) conditionFormat = regexp.MustCompile(`(or|\|\|)`) blankFormat = regexp.MustCompile("blanks|nonblanks") matchFormat = regexp.MustCompile("[*?]") ) // parseTableOptions provides a function to parse the format settings of the // table with default value. func parseTableOptions(opts *Table) (*Table, error) { var err error if opts == nil { return &Table{ShowRowStripes: boolPtr(true)}, err } if opts.ShowRowStripes == nil { opts.ShowRowStripes = boolPtr(true) } if err = checkDefinedName(opts.Name); err != nil { return opts, err } return opts, err } // AddTable provides the method to add table in a worksheet by given worksheet // name, range reference and format set. For example, create a table of A1:D5 // on Sheet1: // // err := f.AddTable("Sheet1", &excelize.Table{Range: "A1:D5"}) // // Create a table of F2:H6 on Sheet2 with format set: // // disable := false // err := f.AddTable("Sheet2", &excelize.Table{ // Range: "F2:H6", // Name: "table", // StyleName: "TableStyleMedium2", // ShowFirstColumn: true, // ShowLastColumn: true, // ShowRowStripes: &disable, // ShowColumnStripes: true, // }) // // Note that the table must be at least two lines including the header. The // header cells must contain strings and must be unique, and must set the // header row data of the table before calling the AddTable function. Multiple // tables range reference that can't have an intersection. // // Name: The name of the table, in the same worksheet name of the table should // be unique, starts with a letter or underscore (_), doesn't include a // space or character, and should be no more than 255 characters // // StyleName: The built-in table style names // // TableStyleLight1 - TableStyleLight21 // TableStyleMedium1 - TableStyleMedium28 // TableStyleDark1 - TableStyleDark11 func (f *File) AddTable(sheet string, table *Table) error { options, err := parseTableOptions(table) if err != nil { return err } var exist bool f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/tables/table") { var t xlsxTable if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))). Decode(&t); err != nil && err != io.EOF { return true } if exist = t.Name == options.Name; exist { return false } } return true }) if exist { return ErrExistsTableName } // Coordinate conversion, convert C1:B3 to 2,0,1,2. coordinates, err := rangeRefToCoordinates(options.Range) if err != nil { return err } // Correct table reference range, such correct C1:B3 to B1:C3. _ = sortCoordinates(coordinates) tableID := f.countTables() + 1 sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl") // Add first table for given sheet. sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") if err = f.addSheetTable(sheet, rID); err != nil { return err } f.addSheetNameSpace(sheet, SourceRelationship) f.clearCalcCache() if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil { return err } return f.addContentTypePart(tableID, "table") } // GetTables provides the method to get all tables in a worksheet by given // worksheet name. func (f *File) GetTables(sheet string) ([]Table, error) { var tables []Table ws, err := f.workSheetReader(sheet) if err != nil { return tables, err } if ws.TableParts == nil { return tables, err } for _, tbl := range ws.TableParts.TableParts { if tbl != nil { target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID) tableXML := strings.ReplaceAll(target, "..", "xl") content, ok := f.Pkg.Load(tableXML) if !ok { continue } var t xlsxTable if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(&t); err != nil && err != io.EOF { return tables, err } table := Table{ rID: tbl.RID, tID: t.ID, tableXML: tableXML, Range: t.Ref, Name: t.Name, } if t.TableStyleInfo != nil { table.StyleName = t.TableStyleInfo.Name table.ShowColumnStripes = t.TableStyleInfo.ShowColumnStripes table.ShowFirstColumn = t.TableStyleInfo.ShowFirstColumn table.ShowLastColumn = t.TableStyleInfo.ShowLastColumn table.ShowRowStripes = &t.TableStyleInfo.ShowRowStripes } tables = append(tables, table) } } return tables, err } // DeleteTable provides the method to delete table by given table name. func (f *File) DeleteTable(name string) error { if err := checkDefinedName(name); err != nil { return err } tbls, err := f.getTables() if err != nil { return err } f.clearCalcCache() for sheet, tables := range tbls { for _, table := range tables { if table.Name != name { continue } ws, _ := f.workSheetReader(sheet) for i, tbl := range ws.TableParts.TableParts { if tbl.RID == table.rID { ws.TableParts.TableParts = append(ws.TableParts.TableParts[:i], ws.TableParts.TableParts[i+1:]...) f.Pkg.Delete(table.tableXML) _ = f.removeContentTypesPart(ContentTypeSpreadSheetMLTable, "/"+table.tableXML) f.deleteSheetRelationships(sheet, tbl.RID) break } } if ws.TableParts.Count = len(ws.TableParts.TableParts); ws.TableParts.Count == 0 { ws.TableParts = nil } return err } } return newNoExistTableError(name) } // getTables provides a function to get all tables in a workbook. func (f *File) getTables() (map[string][]Table, error) { tables := map[string][]Table{} for _, sheetName := range f.GetSheetList() { tbls, err := f.GetTables(sheetName) e := ErrSheetNotExist{sheetName} if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() { return tables, err } tables[sheetName] = append(tables[sheetName], tbls...) } return tables, nil } // countTables provides a function to get table files count storage in the // folder xl/tables. func (f *File) countTables() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/tables/tableSingleCells") { var cells xlsxSingleXMLCells if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))). Decode(&cells); err != nil && err != io.EOF { count++ return true } for _, cell := range cells.SingleXmlCell { if count < cell.ID { count = cell.ID } } } if strings.Contains(k.(string), "xl/tables/table") { var t xlsxTable if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))). Decode(&t); err != nil && err != io.EOF { count++ return true } if count < t.ID { count = t.ID } } return true }) return count } // addSheetTable provides a function to add tablePart element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetTable(sheet string, rID int) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } table := &xlsxTablePart{ RID: "rId" + strconv.Itoa(rID), } if ws.TableParts == nil { ws.TableParts = &xlsxTableParts{} } ws.TableParts.Count++ ws.TableParts.TableParts = append(ws.TableParts.TableParts, table) return err } // setTableColumns provides a function to set cells value in header row for the // table. func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int, tbl *xlsxTable) error { var ( idx int header []string tableColumns []*xlsxTableColumn getTableColumn = func(name string) *xlsxTableColumn { if tbl != nil && tbl.TableColumns != nil { for _, column := range tbl.TableColumns.TableColumn { if column.Name == name { return column } } } return nil } ) for i := x1; i <= x2; i++ { idx++ cell, err := CoordinatesToCellName(i, y1) if err != nil { return err } name, _ := f.GetCellValue(sheet, cell, Options{RawCellValue: true}) if _, err := strconv.Atoi(name); err == nil { if showHeaderRow { _ = f.SetCellStr(sheet, cell, name) } } if name == "" || inStrSlice(header, name, true) != -1 { name = "Column" + strconv.Itoa(idx) if showHeaderRow { _ = f.SetCellStr(sheet, cell, name) } } header = append(header, name) if column := getTableColumn(name); column != nil { column.ID, column.DataDxfID, column.QueryTableFieldID = idx, 0, 0 tableColumns = append(tableColumns, column) continue } tableColumns = append(tableColumns, &xlsxTableColumn{ ID: idx, Name: name, }) } tbl.TableColumns = &xlsxTableColumns{ Count: len(tableColumns), TableColumn: tableColumns, } return nil } // checkDefinedName check whether there are illegal characters in the defined // name or table name. Verify that the name: // 1. Starts with a letter or underscore (_) // 2. Doesn't include a space or character that isn't allowed func checkDefinedName(name string) error { if countUTF16String(name) > MaxFieldLength { return ErrNameLength } inCodeRange := func(code int, tbl []int) bool { for i := 0; i < len(tbl); i += 2 { if tbl[i] <= code && code <= tbl[i+1] { return true } } return false } for i, c := range name { if i == 0 { if inCodeRange(int(c), supportedDefinedNameAtStartCharCodeRange) { continue } return newInvalidNameError(name) } if inCodeRange(int(c), supportedDefinedNameAfterStartCharCodeRange) { continue } return newInvalidNameError(name) } return nil } // addTable provides a function to add table by given worksheet name, // range reference and format set. func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Table) error { // Correct the minimum number of rows, the table at least two lines. if y1 == y2 { y2++ } hideHeaderRow := opts != nil && opts.ShowHeaderRow != nil && !*opts.ShowHeaderRow if hideHeaderRow { y1++ } // Correct table range reference, such correct C1:B3 to B1:C3. ref, err := coordinatesToRangeRef([]int{x1, y1, x2, y2}) if err != nil { return err } name := opts.Name if name == "" { name = "Table" + strconv.Itoa(i) } t := xlsxTable{ XMLNS: NameSpaceSpreadSheet.Value, ID: i, Name: name, DisplayName: name, Ref: ref, AutoFilter: &xlsxAutoFilter{ Ref: ref, }, TableStyleInfo: &xlsxTableStyleInfo{ Name: opts.StyleName, ShowFirstColumn: opts.ShowFirstColumn, ShowLastColumn: opts.ShowLastColumn, ShowRowStripes: *opts.ShowRowStripes, ShowColumnStripes: opts.ShowColumnStripes, }, } _ = f.setTableColumns(sheet, !hideHeaderRow, x1, y1, x2, &t) if hideHeaderRow { t.AutoFilter = nil t.HeaderRowCount = intPtr(0) } table, err := xml.Marshal(t) f.saveFileList(tableXML, table) return err } // AutoFilter provides the method to add auto filter in a worksheet by given // worksheet name, range reference and settings. An auto filter in Excel is a // way of filtering a 2D range of data based on some simple criteria. For // example applying an auto filter to a cell range A1:D4 in the Sheet1: // // err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{}) // // Filter data in an auto filter: // // err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{ // {Column: "B", Expression: "x != blanks"}, // }) // // Column defines the filter columns in an auto filter range based on simple // criteria // // It isn't sufficient to just specify the filter condition. You must also // hide any rows that don't match the filter condition. Rows are hidden using // the SetRowVisible function. Excelize can't filter rows automatically since // this isn't part of the file format. // // Setting a filter criteria for a column: // // Expression defines the conditions, the following operators are available // for setting the filter criteria: // // == // != // > // < // >= // <= // and // or // // An expression can comprise a single statement or two statements separated // by the 'and' and 'or' operators. For example: // // x < 2000 // x > 2000 // x == 2000 // x > 2000 and x < 5000 // x == 2000 or x == 5000 // // Filtering of blank or non-blank data can be achieved by using a value of // Blanks or NonBlanks in the expression: // // x == Blanks // x == NonBlanks // // Excel also allows some simple string matching operations: // // x == b* // begins with b // x != b* // doesn't begin with b // x == *b // ends with b // x != *b // doesn't end with b // x == *b* // contains b // x != *b* // doesn't contain b // // You can also use '*' to match any character or number and '?' to match any // single character or number. No other regular expression quantifier is // supported by Excel's filters. Excel's regular expression characters can be // escaped using '~'. // // The placeholder variable x in the above examples can be replaced by any // simple string. The actual placeholder name is ignored internally so the // following are all equivalent: // // x < 2000 // col < 2000 // Price < 2000 func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) error { coordinates, err := rangeRefToCoordinates(rangeRef) if err != nil { return err } _ = sortCoordinates(coordinates) // Correct reference range, such correct C1:B3 to B1:C3. ref, _ := coordinatesToRangeRef(coordinates, true) wb, err := f.workbookReader() if err != nil { return err } sheetID, err := f.GetSheetIndex(sheet) if err != nil { return err } filterRange := fmt.Sprintf("'%s'!%s", sheet, ref) d := xlsxDefinedName{ Name: builtInDefinedNames[3], Hidden: true, LocalSheetID: intPtr(sheetID), Data: filterRange, } if wb.DefinedNames == nil { wb.DefinedNames = &xlsxDefinedNames{ DefinedName: []xlsxDefinedName{d}, } } else { var definedNameExists bool for idx := range wb.DefinedNames.DefinedName { definedName, localSheetID := wb.DefinedNames.DefinedName[idx], 0 if definedName.LocalSheetID != nil { localSheetID = *definedName.LocalSheetID } if definedName.Name == builtInDefinedNames[3] && localSheetID == sheetID && definedName.Hidden { wb.DefinedNames.DefinedName[idx].Data = filterRange definedNameExists = true } } if !definedNameExists { wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) } } columns := coordinates[2] - coordinates[0] return f.autoFilter(sheet, ref, columns, coordinates[0], opts) } // autoFilter provides a function to extract the tokens from the filter // expression. The tokens are mainly non-whitespace groups. func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilterOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if ws.SheetPr != nil { ws.SheetPr.FilterMode = true } ws.SheetPr = &xlsxSheetPr{FilterMode: true} filter := &xlsxAutoFilter{ Ref: ref, } ws.AutoFilter = filter for _, opt := range opts { if opt.Column == "" || opt.Expression == "" { continue } fsCol, err := ColumnNameToNumber(opt.Column) if err != nil { return err } offset := fsCol - col if offset < 0 || offset > columns { return newInvalidAutoFilterColumnError(opt.Column) } fc := &xlsxFilterColumn{ColID: offset} token := expressionFormat.FindAllString(opt.Expression, -1) if len(token) != 3 && len(token) != 7 { return newInvalidAutoFilterExpError(opt.Expression) } expressions, tokens, err := f.parseFilterExpression(opt.Expression, token) if err != nil { return err } f.writeAutoFilter(fc, expressions, tokens) filter.FilterColumn = append(filter.FilterColumn, fc) } ws.AutoFilter = filter return nil } // writeAutoFilter provides a function to check for single or double custom // filters as default filters and handle them accordingly. func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string) { if len(exp) == 1 && exp[0] == 2 { // Single equality. var filters []*xlsxFilter filters = append(filters, &xlsxFilter{Val: tokens[0]}) fc.Filters = &xlsxFilters{Filter: filters} return } if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 { // Double equality with "or" operator. var filters []*xlsxFilter for _, v := range tokens { filters = append(filters, &xlsxFilter{Val: v}) } fc.Filters = &xlsxFilters{Filter: filters} return } // Non default custom filter. expRel, andRel := map[int]int{0: 0, 1: 2}, map[int]bool{0: true, 1: false} for k, v := range tokens { f.writeCustomFilter(fc, exp[expRel[k]], v) if k == 1 { fc.CustomFilters.And = andRel[exp[k]] } } } // writeCustomFilter provides a function to write the element. func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string) { operators := map[int]string{ 1: "lessThan", 2: "equal", 3: "lessThanOrEqual", 4: "greaterThan", 5: "notEqual", 6: "greaterThanOrEqual", 22: "equal", } customFilter := xlsxCustomFilter{ Operator: operators[operator], Val: val, } if fc.CustomFilters != nil { fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter) return } var customFilters []*xlsxCustomFilter customFilters = append(customFilters, &customFilter) fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters} } // parseFilterExpression provides a function to converts the tokens of a // possibly conditional expression into 1 or 2 sub expressions for further // parsing. // // Examples: // // ('x', '==', 2000) -> exp1 // ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2 func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) { var expressions []int var t []string if len(tokens) == 7 { // The number of tokens will be either 3 (for 1 expression) or 7 (for 2 // expressions). conditional, c := 0, tokens[3] if conditionFormat.MatchString(c) { conditional = 1 } expression1, token1, err := f.parseFilterTokens(expression, tokens[:3]) if err != nil { return expressions, t, err } expression2, token2, err := f.parseFilterTokens(expression, tokens[4:7]) if err != nil { return expressions, t, err } return []int{expression1[0], conditional, expression2[0]}, []string{token1, token2}, nil } exp, token, err := f.parseFilterTokens(expression, tokens) if err != nil { return expressions, t, err } return exp, []string{token}, nil } // parseFilterTokens provides a function to parse the 3 tokens of a filter // expression and return the operator and token. func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, string, error) { operators := map[string]int{ "==": 2, "=": 2, "=~": 2, "eq": 2, "!=": 5, "!~": 5, "ne": 5, "<>": 5, "<": 1, "<=": 3, ">": 4, ">=": 6, } operator, ok := operators[strings.ToLower(tokens[1])] if !ok { // Convert the operator from a number to a descriptive string. return []int{}, "", newUnknownFilterTokenError(tokens[1]) } token := tokens[2] // Special handling for Blanks/NonBlanks. re := blankFormat.MatchString(strings.ToLower(token)) if re { // Only allow Equals or NotEqual in this context. if operator != 2 && operator != 5 { return []int{operator}, token, newInvalidAutoFilterOperatorError(tokens[1], expression) } token = strings.ToLower(token) // The operator should always be 2 (=) to flag a "simple" equality in // the binary record. Therefore we convert <> to =. if token == "blanks" { if operator == 5 { token = " " } } else { if operator == 5 { operator = 2 token = "blanks" } else { operator = 5 token = " " } } } // If the string token contains an Excel match character then change the // operator type to indicate a non "simple" equality. if re = matchFormat.MatchString(token); operator == 2 && re { operator = 22 } return []int{operator}, token, nil } ================================================ FILE: table_test.go ================================================ package excelize import ( "fmt" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestAddTable(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"})) assert.NoError(t, f.AddTable("Sheet2", &Table{ Range: "A2:B5", Name: "table", StyleName: "TableStyleMedium2", ShowColumnStripes: true, ShowFirstColumn: true, ShowLastColumn: true, ShowRowStripes: boolPtr(true), })) assert.NoError(t, f.AddTable("Sheet2", &Table{ Range: "D1:D11", ShowHeaderRow: boolPtr(false), })) assert.NoError(t, f.AddTable("Sheet2", &Table{Range: "F1:F1", StyleName: "TableStyleMedium8"})) // Test get tables in worksheet tables, err := f.GetTables("Sheet2") assert.Len(t, tables, 3) assert.NoError(t, err) // Test add table with already exist table name assert.Equal(t, f.AddTable("Sheet2", &Table{Name: "Table1"}), ErrExistsTableName) // Test add table with invalid table options assert.Equal(t, f.AddTable("Sheet1", nil), ErrParameterInvalid) // Test add table in not exist worksheet assert.EqualError(t, f.AddTable("SheetN", &Table{Range: "B26:A21"}), "sheet SheetN does not exist") // Test add table with illegal cell reference assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx"))) // Test add table with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.AddTable("Sheet:1", &Table{Range: "B26:A21"})) // Test addTable with illegal cell reference f = NewFile() assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil)) assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil)) // Test set defined name and add table with invalid name for _, cases := range []struct { name string err error }{ {name: "1Table", err: newInvalidNameError("1Table")}, {name: "-Table", err: newInvalidNameError("-Table")}, {name: "'Table", err: newInvalidNameError("'Table")}, {name: "Table 1", err: newInvalidNameError("Table 1")}, {name: "A&B", err: newInvalidNameError("A&B")}, {name: "_1Table'", err: newInvalidNameError("_1Table'")}, {name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidNameError("\u0f5f\u0fb3\u0f0b\u0f21")}, {name: strings.Repeat("c", MaxFieldLength+1), err: ErrNameLength}, } { assert.Equal(t, cases.err, f.AddTable("Sheet1", &Table{ Range: "A1:B2", Name: cases.name, })) assert.Equal(t, cases.err, f.SetDefinedName(&DefinedName{ Name: cases.name, RefersTo: "Sheet1!$A$2:$D$5", })) } // Test check duplicate table name with unsupported charset table parts f = NewFile() f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"})) assert.NoError(t, f.Close()) f = NewFile() // Test add table with workbook with single cells parts f.Pkg.Store("xl/tables/tableSingleCells1.xml", []byte("")) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"})) // Test add table with workbook with unsupported charset single cells parts f.Pkg.Store("xl/tables/tableSingleCells1.xml", MacintoshCyrillicCharset) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"})) assert.NoError(t, f.Close()) } func TestGetTables(t *testing.T) { f := NewFile() // Test get tables in none table worksheet tables, err := f.GetTables("Sheet1") assert.Len(t, tables, 0) assert.NoError(t, err) // Test get tables in not exist worksheet _, err = f.GetTables("SheetN") assert.EqualError(t, err, "sheet SheetN does not exist") // Test adjust table with unsupported charset assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"})) f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) _, err = f.GetTables("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test adjust table with no exist table parts f.Pkg.Delete("xl/tables/table1.xml") tables, err = f.GetTables("Sheet1") assert.Len(t, tables, 0) assert.NoError(t, err) } func TestDeleteTable(t *testing.T) { f := NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"})) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21", Name: "Table2"})) assert.NoError(t, f.DeleteTable("Table2")) assert.NoError(t, f.DeleteTable("Table1")) // Test delete table with invalid table name assert.Equal(t, newInvalidNameError("Table 1"), f.DeleteTable("Table 1")) // Test delete table with no exist table name assert.Equal(t, newNoExistTableError("Table"), f.DeleteTable("Table")) // Test delete table with unsupported charset f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.DeleteTable("Table1"), "XML syntax error on line 1: invalid UTF-8") // Test delete table without deleting table header f = NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Date")) assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Values")) assert.NoError(t, f.UpdateLinkedValue()) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2", Name: "Table1"})) assert.NoError(t, f.DeleteTable("Table1")) val, err := f.GetCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "Date", val) val, err = f.GetCellValue("Sheet1", "B1") assert.NoError(t, err) assert.Equal(t, "Values", val) } func TestSetTableColumns(t *testing.T) { f := NewFile() assert.Equal(t, newCoordinatesToCellNameError(1, 0), f.setTableColumns("Sheet1", true, 1, 0, 1, nil)) } func TestAutoFilter(t *testing.T) { outFile := filepath.Join("test", "TestAutoFilter%d.xlsx") f, err := prepareTestBook1() assert.NoError(t, err) for i, opts := range [][]AutoFilterOptions{ {}, {{Column: "B", Expression: ""}}, {{Column: "B", Expression: "x != blanks"}}, {{Column: "B", Expression: "x == blanks"}}, {{Column: "B", Expression: "x != nonblanks"}}, {{Column: "B", Expression: "x == nonblanks"}}, {{Column: "B", Expression: "x <= 1 and x >= 2"}}, {{Column: "B", Expression: "x == 1 or x == 2"}}, {{Column: "B", Expression: "x == 1 or x == 2*"}}, } { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts)) assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) }) } // Test add auto filter with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.AutoFilter("Sheet:1", "A1:B1", nil)) // Test add auto filter with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AutoFilter("Sheet1", "A:B1", nil)) assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), f.AutoFilter("Sheet1", "A1:B", nil)) // Test add auto filter with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8") // Test add auto filter with empty local sheet ID f = NewFile() f.WorkBook = &xlsxWorkbook{DefinedNames: &xlsxDefinedNames{DefinedName: []xlsxDefinedName{{Name: builtInDefinedNames[3], Hidden: true}}}} assert.NoError(t, f.AutoFilter("Sheet1", "A1:B1", nil)) } func TestAutoFilterError(t *testing.T) { outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx") f, err := prepareTestBook1() assert.NoError(t, err) for i, opts := range [][]AutoFilterOptions{ {{Column: "B", Expression: "x <= 1 and x >= blanks"}}, {{Column: "B", Expression: "x -- y or x == *2*"}}, {{Column: "B", Expression: "x != y or x ? *2"}}, {{Column: "B", Expression: "x -- y o r x == *2"}}, {{Column: "B", Expression: "x -- y"}}, {{Column: "A", Expression: "x -- y"}}, } { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) { assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) } }) } assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.autoFilter("SheetN", "A1", 1, 1, []AutoFilterOptions{{ Column: "A", Expression: "", }})) assert.Equal(t, newInvalidColumnNameError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{ Column: "-", Expression: "-", }})) assert.Equal(t, newInvalidAutoFilterColumnError("A"), f.autoFilter("Sheet1", "A1", 1, 100, []AutoFilterOptions{{ Column: "A", Expression: "-", }})) assert.Equal(t, newInvalidAutoFilterExpError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{ Column: "A", Expression: "-", }})) } func TestParseFilterTokens(t *testing.T) { f := NewFile() // Test with unknown operator _, _, err := f.parseFilterTokens("", []string{"", "!"}) assert.EqualError(t, err, "unknown operator: !") // Test invalid operator in context _, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"}) assert.Equal(t, newInvalidAutoFilterOperatorError("<", ""), err) } ================================================ FILE: templates.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. // // This file contains default templates for XML files we don't yet populated // based on content. package excelize import "encoding/xml" // Source relationship and namespace list, associated prefixes and schema in which it was // introduced. var ( NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"} NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"} NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"} NameSpaceDrawingMLA14 = xml.Attr{Name: xml.Name{Local: "a14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2010/main"} NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"} NameSpaceDrawingMLSlicer = xml.Attr{Name: xml.Name{Local: "sle", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2010/slicer"} NameSpaceDrawingMLSlicerX15 = xml.Attr{Name: xml.Name{Local: "sle15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2012/slicer"} NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"} NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"} NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"} NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"} NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"} NameSpaceSpreadSheetXR10 = xml.Attr{Name: xml.Name{Local: "xr10", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"} SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"} SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"} SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"} SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"} SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"} ) // Source relationship and namespace. const ( ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml" ContentTypeCustomProperties = "application/vnd.openxmlformats-officedocument.custom-properties+xml" ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml" ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" ContentTypeRelationships = "application/vnd.openxmlformats-package.relationships+xml" ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" ContentTypeSlicer = "application/vnd.ms-excel.slicer+xml" ContentTypeSlicerCache = "application/vnd.ms-excel.slicerCache+xml" ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" ContentTypeVBA = "application/vnd.ms-office.vbaProject" ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing" NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main" NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/" NameSpaceExtendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" NameSpaceXML = "http://www.w3.org/XML/1998/namespace" NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance" SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet" SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" SourceRelationshipCustomProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet" SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" SourceRelationshipExtendProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" SourceRelationshipSlicer = "http://schemas.microsoft.com/office/2007/relationships/slicer" SourceRelationshipSlicerCache = "http://schemas.microsoft.com/office/2007/relationships/slicerCache" SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes" StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main" StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties" StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main" StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships" StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart" StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments" StrictSourceRelationshipExtendProperties = "http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties" StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image" StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" // The following constants defined the extLst child element // ([ISO/IEC29500-1:2016] section 18.2.10) of the workbook and worksheet // elements extended by the addition of new child ext elements. ExtURICalcFeatures = "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}" ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}" EXtURICustomPropertyFmtID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" ExtURIDataField = "{E15A36E0-9728-4E99-A89B-3F7291B0FE68}" ExtURIDataModel = "{FCE2AD5D-F65C-4FA6-A056-5C36A1767C68}" ExtURIDataValidations = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}" ExtURIExternalLinkPr = "{FCE6A71B-6B00-49CD-AB44-F6B1AE7CDE65}" ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}" ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}" ExtURIModelTimeGroupings = "{9835A34E-60A6-4A7C-AAB8-D5F71C897F49}" ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}" ExtURIPivotCachesX14 = "{876F7934-8845-4945-9796-88D515C7AA90}" ExtURIPivotCachesX15 = "{841E416B-1EF1-43b6-AB56-02D37102CBD5}" ExtURIPivotField = "{2946ED86-A175-432a-8AC1-64E0C546D7DE}" ExtURIPivotFilter = "{0605FD5F-26C8-4aeb-8148-2DB25E43C511}" ExtURIPivotHierarchy = "{F1805F06-0CD304483-9156-8803C3D141DF}" ExtURIPivotTableReferences = "{983426D0-5260-488c-9760-48F4B6AC55F4}" ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}" ExtURISlicerCacheDefinition = "{2F2917AC-EB37-4324-AD4E-5DD8C200BD13}" ExtURISlicerCacheHideItemsWithNoData = "{470722E0-AACD-4C17-9CDC-17EF765DBC7E}" ExtURISlicerCachesX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}" ExtURISlicerCachesX15 = "{46BE6895-7355-4a93-B00E-2C351335B9C9}" ExtURISlicerListX14 = "{A8765BA9-456A-4dab-B4F3-ACF838C121DE}" ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}" ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" ExtURITimelineCachePivotCaches = "{A2CB5862-8E78-49c6-8D9D-AF26E26ADB89}" ExtURITimelineCacheRefs = "{D0CA8CA8-9F24-4464-BF8E-62219DCF47F9}" ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}" ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" ExtURIWorkbookPrX14 = "{79F54976-1DA5-4618-B147-ACDE4B953A38}" ExtURIWorkbookPrX15 = "{140A7094-0E35-4892-8432-C4D2E57EDEB5}" ) // workbookExtURIPriority is the priority of URI in the workbook extension lists. var workbookExtURIPriority = []string{ ExtURIPivotCachesX14, ExtURISlicerCachesX14, ExtURISlicerCachesX15, ExtURIWorkbookPrX14, ExtURIPivotCachesX15, ExtURIPivotTableReferences, ExtURITimelineCachePivotCaches, ExtURITimelineCacheRefs, ExtURIWorkbookPrX15, ExtURIDataModel, ExtURICalcFeatures, ExtURIExternalLinkPr, ExtURIModelTimeGroupings, } // worksheetExtURIPriority is the priority of URI in the worksheet extension lists. var worksheetExtURIPriority = []string{ ExtURIConditionalFormattings, ExtURIDataValidations, ExtURISparklineGroups, ExtURISlicerListX14, ExtURIProtectedRanges, ExtURIIgnoredErrors, ExtURIWebExtensions, ExtURISlicerListX15, ExtURITimelineRefs, ExtURIExternalLinkPr, } // Excel specifications and limits const ( EMU = 9525 MaxCellStyles = 65430 MaxColumns = 16384 MaxColumnWidth = 255 MaxFieldLength = 255 MaxFilePathLength = 207 MaxFormControlValue = 30000 MaxFontFamilyLength = 31 MaxGraphicAltTextLength = 65535 MaxGraphicNameLength = 254 MaxFontSize = 409 MaxRowHeight = 409 MaxSheetNameLength = 31 MinColumns = 1 MinFontSize = 1 StreamChunkSize = 1 << 24 TotalCellChars = 32767 TotalRows = 1048576 TotalSheetHyperlinks = 65529 UnzipSizeLimit = 1000 << 24 // pivotTableVersion should be greater than 3. One or more of the // PivotTables chosen are created in a version of Excel earlier than // Excel 2007 or in compatibility mode. Slicer can only be used with // PivotTables created in Excel 2007 or a newer version of Excel. pivotTableVersion = 3 pivotTableRefreshedVersion = 8 defaultDrawingScale = 1.0 defaultChartDimensionWidth = 480 defaultChartDimensionHeight = 260 defaultSlicerWidth = 200 defaultSlicerHeight = 200 defaultChartLegendPosition = "bottom" defaultChartShowBlanksAs = "gap" defaultShapeSize = 160 defaultShapeLineWidth = 1 defaultColWidth = 9.140625 defaultColWidthPixels = 64.0 defaultRowHeight = 15.0 defaultRowHeightPixels = 20.0 defaultFontSize = 11.0 ) // ColorMappingType is the type of color transformation. type ColorMappingType byte // Color transformation types enumeration. const ( ColorMappingTypeLight1 ColorMappingType = iota ColorMappingTypeDark1 ColorMappingTypeLight2 ColorMappingTypeDark2 ColorMappingTypeAccent1 ColorMappingTypeAccent2 ColorMappingTypeAccent3 ColorMappingTypeAccent4 ColorMappingTypeAccent5 ColorMappingTypeAccent6 ColorMappingTypeHyperlink ColorMappingTypeFollowedHyperlink ColorMappingTypeUnset int = -1 ) // ChartDataLabelPositionType is the type of chart data labels position. type ChartDataLabelPositionType byte // Chart data labels positions types enumeration. const ( ChartDataLabelsPositionUnset ChartDataLabelPositionType = iota ChartDataLabelsPositionBestFit ChartDataLabelsPositionBelow ChartDataLabelsPositionCenter ChartDataLabelsPositionInsideBase ChartDataLabelsPositionInsideEnd ChartDataLabelsPositionLeft ChartDataLabelsPositionOutsideEnd ChartDataLabelsPositionRight ChartDataLabelsPositionAbove ) // chartDataLabelsPositionTypes defined supported chart data labels position // types. var chartDataLabelsPositionTypes = map[ChartDataLabelPositionType]string{ ChartDataLabelsPositionBestFit: "bestFit", ChartDataLabelsPositionBelow: "b", ChartDataLabelsPositionCenter: "ctr", ChartDataLabelsPositionInsideBase: "inBase", ChartDataLabelsPositionInsideEnd: "inEnd", ChartDataLabelsPositionLeft: "l", ChartDataLabelsPositionOutsideEnd: "outEnd", ChartDataLabelsPositionRight: "r", ChartDataLabelsPositionAbove: "t", } // chartDashTypes defined supported preset chart dash types. var chartDashTypes = map[ChartDashType]string{ ChartDashSolid: "solid", ChartDashDot: "dot", ChartDashDash: "dash", ChartDashLgDash: "lgDash", ChartDashSashDot: "dashDot", ChartDashLgDashDot: "lgDashDot", ChartDashLgDashDotDot: "lgDashDotDot", ChartDashSysDash: "sysDash", ChartDashSysDot: "sysDot", ChartDashSysDashDot: "sysDashDot", ChartDashSysDashDotDot: "sysDashDotDot", } // supportedChartDataLabelsPosition defined supported chart data labels position // types for each type of chart. var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionType{ Bar: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, BarStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, BarPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, Col: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, ColStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, ColPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, Line: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, Pie: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, Pie3D: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, Scatter: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, Bubble: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, Bubble3D: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, } const ( defaultTempFileSST = "sharedStrings" defaultXMLMetadata = "xl/metadata.xml" defaultXMLPathCalcChain = "xl/calcChain.xml" defaultXMLPathCellImages = "xl/cellimages.xml" defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels" defaultXMLPathContentTypes = "[Content_Types].xml" defaultXMLPathDocPropsApp = "docProps/app.xml" defaultXMLPathDocPropsCore = "docProps/core.xml" defaultXMLPathDocPropsCustom = "docProps/custom.xml" defaultXMLPathRels = "_rels/.rels" defaultXMLPathSharedStrings = "xl/sharedStrings.xml" defaultXMLPathSheet = "xl/worksheets/sheet1.xml" defaultXMLPathStyles = "xl/styles.xml" defaultXMLPathTheme = "xl/theme/theme1.xml" defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml" defaultXMLPathWorkbook = "xl/workbook.xml" defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels" defaultXMLRdRichValue = "xl/richData/rdrichvalue.xml" defaultXMLRdRichValueRel = "xl/richData/richValueRel.xml" defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels" defaultXMLRdRichValueStructure = "xl/richData/rdrichvaluestructure.xml" defaultXMLRdRichValueTypes = "xl/richData/rdRichValueTypes.xml" defaultXMLRdRichValueWebImage = "xl/richData/rdRichValueWebImage.xml" defaultXMLRdRichValueWebImageRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels" ) // IndexedColorMapping is the table of default mappings from indexed color value // to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards // compatibility. A legacy indexing scheme for colors that is still required // for some records, and for backwards compatibility with legacy formats. This // element contains a sequence of RGB color values that correspond to color // indexes (zero-based). When using the default indexed color palette, the // values are not written out, but instead are implied. When the color palette // has been modified from default, then the entire color palette is written // out. var IndexedColorMapping = []string{ "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080", "9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF", "000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF", "00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99", "3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696", "003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333", "000000", "FFFFFF", } // supportedDefinedNameAtStartCharCodeRange list the valid first character of a // defined name ASCII letters. var supportedDefinedNameAtStartCharCodeRange = []int{ 65, 90, 92, 92, 95, 95, 97, 122, 161, 161, 164, 164, 167, 168, 170, 170, 173, 173, 175, 186, 188, 696, 699, 705, 711, 711, 713, 715, 717, 717, 720, 721, 728, 731, 733, 733, 736, 740, 750, 750, 880, 883, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1315, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2418, 2427, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3389, 3389, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3642, 3648, 3662, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3979, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346, 4348, 4348, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6569, 6593, 6599, 6656, 6678, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7168, 7203, 7245, 7247, 7258, 7293, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8208, 8208, 8211, 8214, 8216, 8216, 8220, 8221, 8224, 8225, 8229, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8305, 8305, 8308, 8308, 8319, 8319, 8321, 8324, 8336, 8340, 8450, 8451, 8453, 8453, 8455, 8455, 8457, 8467, 8469, 8470, 8473, 8477, 8481, 8482, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8531, 8532, 8539, 8542, 8544, 8584, 8592, 8601, 8658, 8658, 8660, 8660, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9397, 9424, 9449, 9472, 9547, 9552, 9588, 9601, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 11264, 11310, 11312, 11358, 11360, 11375, 11377, 11389, 11392, 11492, 11520, 11557, 11568, 11621, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12288, 12291, 12293, 12311, 12317, 12319, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12543, 12549, 12589, 12593, 12686, 12704, 12727, 12784, 12828, 12832, 12841, 12849, 12850, 12857, 12857, 12896, 12923, 12927, 12927, 12963, 12968, 13059, 13059, 13069, 13069, 13076, 13076, 13080, 13080, 13090, 13091, 13094, 13095, 13099, 13099, 13110, 13110, 13115, 13115, 13129, 13130, 13133, 13133, 13137, 13137, 13143, 13143, 13179, 13182, 13184, 13188, 13192, 13258, 13261, 13267, 13269, 13270, 13272, 13272, 13275, 13277, 13312, 19893, 19968, 40899, 40960, 42124, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42591, 42594, 42606, 42624, 42647, 42786, 42887, 42891, 42892, 43003, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43274, 43301, 43312, 43334, 43520, 43560, 43584, 43586, 43588, 43595, 44032, 55203, 57344, 63560, 63744, 64045, 64048, 64106, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65072, 65073, 65075, 65092, 65097, 65106, 65108, 65111, 65113, 65126, 65128, 65131, 65136, 65140, 65142, 65276, 65281, 65374, 65377, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65504, 65510, } // supportedDefinedNameAfterStartCharCodeRange list the valid after first // character of a defined name ASCII letters. var supportedDefinedNameAfterStartCharCodeRange = []int{ 46, 46, 48, 57, 63, 63, 65, 90, 92, 92, 95, 95, 97, 122, 161, 161, 164, 164, 167, 168, 170, 170, 173, 173, 175, 186, 188, 887, 890, 893, 900, 902, 904, 906, 908, 908, 910, 929, 931, 1315, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1536, 1539, 1542, 1544, 1547, 1547, 1550, 1562, 1567, 1567, 1569, 1630, 1632, 1641, 1646, 1747, 1749, 1791, 1807, 1866, 1869, 1969, 1984, 2038, 2042, 2042, 2305, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2417, 2418, 2427, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2554, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2801, 2801, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3066, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3192, 3199, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3389, 3396, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3427, 3430, 3445, 3449, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3647, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3843, 3859, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4030, 4044, 4046, 4047, 4096, 4169, 4176, 4249, 4254, 4293, 4304, 4346, 4348, 4348, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4959, 4960, 4969, 4988, 4992, 5017, 5024, 5108, 5121, 5740, 5743, 5750, 5760, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6107, 6109, 6112, 6121, 6128, 6137, 6155, 6158, 6160, 6169, 6176, 6263, 6272, 6314, 6400, 6428, 6432, 6443, 6448, 6459, 6464, 6464, 6470, 6509, 6512, 6516, 6528, 6569, 6576, 6601, 6608, 6617, 6624, 6683, 6912, 6987, 6992, 7001, 7009, 7036, 7040, 7082, 7086, 7097, 7168, 7223, 7232, 7241, 7245, 7293, 7424, 7654, 7678, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8132, 8134, 8147, 8150, 8155, 8157, 8175, 8178, 8180, 8182, 8190, 8192, 8208, 8211, 8214, 8216, 8216, 8220, 8221, 8224, 8225, 8229, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8260, 8260, 8274, 8274, 8287, 8292, 8298, 8305, 8308, 8316, 8319, 8332, 8336, 8340, 8352, 8373, 8400, 8432, 8448, 8527, 8531, 8584, 8592, 9000, 9003, 9191, 9216, 9254, 9280, 9290, 9312, 9885, 9888, 9916, 9920, 9923, 9985, 9988, 9990, 9993, 9996, 10023, 10025, 10059, 10061, 10061, 10063, 10066, 10070, 10070, 10072, 10078, 10081, 10087, 10102, 10132, 10136, 10159, 10161, 10174, 10176, 10180, 10183, 10186, 10188, 10188, 10192, 10213, 10224, 10626, 10649, 10711, 10716, 10747, 10750, 11084, 11088, 11092, 11264, 11310, 11312, 11358, 11360, 11375, 11377, 11389, 11392, 11498, 11517, 11517, 11520, 11557, 11568, 11621, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12283, 12288, 12311, 12317, 12335, 12337, 12348, 12350, 12351, 12353, 12438, 12441, 12447, 12449, 12543, 12549, 12589, 12593, 12686, 12688, 12727, 12736, 12771, 12784, 12830, 12832, 12867, 12880, 13054, 13056, 19893, 19904, 40899, 40960, 42124, 42128, 42182, 42240, 42508, 42512, 42539, 42560, 42591, 42594, 42610, 42620, 42621, 42623, 42647, 42752, 42892, 43003, 43051, 43072, 43123, 43136, 43204, 43216, 43225, 43264, 43310, 43312, 43347, 43520, 43574, 43584, 43597, 43600, 43609, 44032, 55203, 55296, 64045, 64048, 64106, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65021, 65024, 65039, 65056, 65062, 65072, 65073, 65075, 65092, 65097, 65106, 65108, 65111, 65113, 65126, 65128, 65131, 65136, 65140, 65142, 65276, 65279, 65279, 65281, 65374, 65377, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65504, 65510, 65512, 65518, 65529, 65533, } // supportedFontWidthFactors defines the average character width ratio of common // font families. This is used for more accurate column width calculation when // different font families are used. var supportedFontWidthFactors = map[string][]float64{ "calibri": {0.97, 1.30, 1.00}, "agency fb": {0.69, 0.86, 1.00}, "aptos": {1.03, 1.37, 1.00}, "arial black": {1.35, 1.59, 1.00}, "arial narrow": {0.88, 1.20, 1.00}, "arial rounded mt bold": {1.07, 1.57, 1.00}, "arial unicode ms": {1.07, 1.42, 1.07}, "arial": {1.07, 1.42, 1.00}, "bahnschrift": {1.10, 1.30, 1.00}, "baskerville old face": {0.98, 1.56, 1.00}, "batang": {1.05, 1.31, 0.99}, "batangche": {1.10, 1.10, 1.10}, "bauhaus 93": {1.10, 1.43, 1.00}, "bell mt": {0.95, 1.33, 1.00}, "berlin sans fb": {1.03, 1.36, 1.00}, "bernard mt condensed": {0.94, 1.24, 1.00}, "blackadder itc": {0.76, 2.12, 1.00}, "bodoni mt condensed": {0.82, 1.17, 1.00}, "bodoni mt poster compressed": {0.72, 1.06, 1.00}, "bodoni mt": {1.06, 1.47, 1.00}, "book antiqua": {1.12, 1.44, 1.00}, "bookman old style": {1.14, 1.66, 1.00}, "broadway": {1.50, 1.75, 1.00}, "brush script mt": {0.82, 1.34, 1.00}, "calibri light": {0.98, 1.19, 1.00}, "calisto mt": {1.03, 1.42, 1.00}, "cambria math": {1.07, 1.28, 1.00}, "cambria": {1.07, 1.28, 1.00}, "candara light": {1.02, 1.35, 1.00}, "cascadia mono extralight": {1.42, 1.49, 1.00}, "castellar": {1.48, 1.58, 1.00}, "centaur": {0.93, 1.30, 1.00}, "century gothic": {1.19, 1.36, 1.00}, "century schoolbook": {1.10, 1.63, 1.00}, "century": {1.09, 1.52, 1.00}, "chiller": {0.90, 1.22, 1.00}, "colonna mt": {1.10, 1.40, 1.00}, "comic sans ms": {1.14, 1.46, 1.00}, "consolas": {1.21, 1.21, 1.00}, "cooper black": {1.28, 1.60, 1.00}, "copperplate gothic bold": {1.32, 1.62, 1.00}, "copperplate gothic light": {1.26, 1.58, 1.00}, "corbel light": {0.98, 1.26, 1.00}, "corbel": {1.03, 1.30, 1.00}, "courier new": {1.45, 1.45, 1.00}, "curlz mt": {1.16, 1.47, 1.00}, "david": {1.25, 1.28, 1.00}, "dengxian light": {1.02, 1.26, 1.02}, "dengxian": {1.05, 1.28, 1.08}, "dfkai-sb": {1.10, 1.10, 1.10}, "dotum": {1.25, 1.31, 0.99}, "dotumche": {1.10, 1.10, 1.10}, "dubai": {1.00, 1.32, 1.00}, "ebrima": {1.08, 1.31, 1.00}, "edwardian script itc": {0.82, 1.78, 1.00}, "elephant": {1.24, 1.86, 1.00}, "engravers mt": {1.99, 1.99, 1.00}, "eras bold itc": {1.14, 1.45, 1.00}, "eras demi itc": {1.10, 1.42, 1.00}, "eras light itc": {1.07, 1.40, 1.00}, "eras medium itc": {1.10, 1.42, 1.00}, "fangsong_gb2312": {1.20, 1.36, 1.10}, "felix titling": {1.20, 1.55, 1.00}, "footlight mt light": {1.03, 1.37, 1.00}, "forte": {0.93, 1.25, 1.00}, "franklin gothic demi cond": {0.88, 1.20, 1.00}, "franklin gothic demi": {1.08, 1.42, 1.00}, "franklin gothic heavy": {1.16, 1.45, 1.00}, "franklin gothic medium": {1.08, 1.42, 1.00}, "freestyle script": {0.87, 1.18, 1.00}, "french script mt": {0.82, 1.54, 1.00}, "fzshuti": {1.10, 1.36, 1.10}, "fzyaoti": {1.10, 1.36, 1.10}, "gabriola": {0.95, 1.28, 1.00}, "garamond": {1.16, 1.39, 1.00}, "georgia": {1.19, 1.43, 1.00}, "gigi": {0.95, 1.28, 1.00}, "gill sans mt condensed": {0.85, 1.18, 1.00}, "gill sans mt": {1.03, 1.36, 1.00}, "gill sans ultra bold": {1.30, 1.60, 1.00}, "gloucester mt extra condensed": {0.72, 1.02, 1.00}, "goudy old style": {0.98, 1.37, 1.00}, "goudy stout": {2.56, 2.80, 1.00}, "gulim": {1.19, 1.31, 0.99}, "gulimche": {1.20, 1.36, 1.10}, "gungsuh": {1.39, 1.39, 0.99}, "gungsuhche": {1.28, 1.36, 1.10}, "haettenschweiler": {0.82, 1.02, 1.00}, "harlow solid italic": {0.90, 1.82, 1.00}, "harrington": {1.07, 1.40, 1.00}, "helvetica neue": {1.10, 1.42, 1.00}, "helvetica": {1.07, 1.42, 1.00}, "high tower text": {0.97, 1.53, 1.00}, "impact": {1.01, 1.12, 1.00}, "informal roman": {1.07, 1.40, 1.00}, "ink free": {1.02, 1.33, 1.00}, "javanese text": {1.10, 1.42, 1.00}, "jokerman": {1.26, 1.67, 1.00}, "juice itc": {0.82, 1.14, 1.00}, "kaiti_gb2312": {1.20, 1.20, 1.10}, "kaiti": {1.20, 1.20, 1.10}, "kartika": {1.10, 1.32, 1.00}, "kokila": {1.17, 1.30, 1.00}, "kristen itc": {1.10, 1.42, 1.00}, "kunstler script": {0.72, 1.72, 1.00}, "latha": {1.17, 1.28, 1.00}, "leelawadee ui": {1.08, 1.31, 1.00}, "leelawadee": {1.03, 1.36, 1.00}, "lisu": {1.10, 1.42, 1.00}, "lucida bright": {1.18, 1.47, 1.00}, "lucida calligraphy": {1.18, 1.67, 1.00}, "lucida console": {1.32, 1.32, 1.00}, "lucida fax": {1.14, 1.45, 1.00}, "lucida handwriting": {1.39, 1.67, 1.00}, "lucida sans typewriter": {1.32, 1.32, 1.00}, "lucida sans unicode": {1.18, 1.47, 1.00}, "lucida sans": {1.18, 1.47, 1.00}, "lxgw wenkai": {1.10, 1.60, 1.10}, "magneto": {1.48, 1.98, 1.00}, "maiandra gd": {1.10, 1.42, 1.00}, "malgun gothic semilight": {1.07, 1.51, 1.07}, "malgun gothic": {1.10, 1.33, 1.10}, "mangal": {1.10, 1.42, 1.00}, "meiryo": {1.19, 1.42, 1.19}, "microsoft himalaya": {0.85, 0.96, 1.00}, "microsoft jhenghei light": {1.10, 1.30, 1.10}, "microsoft jhenghei ui": {1.16, 1.39, 1.16}, "microsoft jhenghei": {1.16, 1.39, 1.16}, "microsoft new tai lue": {1.03, 1.38, 1.00}, "microsoft phagspa": {1.00, 1.30, 1.00}, "microsoft sans serif": {1.07, 1.40, 1.00}, "microsoft tai le": {1.00, 1.30, 1.00}, "microsoft uighur": {0.90, 1.10, 1.00}, "microsoft yahei light": {1.10, 1.30, 1.10}, "microsoft yahei ui": {1.18, 1.30, 1.18}, "microsoft yahei": {1.18, 1.30, 1.18}, "microsoft yi baiti": {1.00, 1.12, 1.00}, "mingliu_hkscs": {1.10, 1.20, 1.10}, "mingliu-extb": {1.10, 1.20, 1.10}, "mingliu": {1.10, 1.20, 1.10}, "mistral": {0.87, 0.90, 1.00}, "modern no. 20": {0.90, 1.42, 1.00}, "mongolian baiti": {0.95, 1.38, 1.00}, "monotype corsiva": {0.85, 1.26, 1.00}, "ms gothic": {1.10, 1.10, 1.10}, "ms pgothic": {0.99, 1.31, 0.99}, "ms reference sans serif": {1.18, 1.47, 1.00}, "ms sans serif": {1.10, 1.42, 1.00}, "narkisim": {1.10, 1.25, 1.00}, "niagara engraved": {0.55, 0.58, 1.00}, "niagara solid": {0.48, 0.58, 1.00}, "nirmala ui semilight": {1.01, 1.32, 1.00}, "nirmala ui": {1.03, 1.36, 1.00}, "noto sans cjk": {1.10, 1.33, 1.10}, "noto serif cjk": {1.10, 1.33, 1.10}, "nsimsun": {1.10, 1.10, 1.10}, "nyala": {1.15, 1.32, 1.00}, "onyx": {0.68, 0.98, 1.00}, "palace script mt": {0.72, 1.22, 1.00}, "palatino linotype": {1.12, 1.44, 1.00}, "papyrus": {1.10, 1.82, 1.00}, "parchment": {0.74, 1.84, 1.00}, "perpetua titling mt": {1.23, 1.52, 1.00}, "perpetua": {0.95, 1.33, 1.00}, "playbill": {0.72, 1.02, 1.00}, "poor richard": {0.97, 1.36, 1.00}, "pristina": {0.87, 1.18, 1.00}, "rage italic": {0.82, 1.14, 1.00}, "ravie": {1.55, 1.86, 1.00}, "rockwell condensed": {0.90, 1.22, 1.00}, "rockwell extra bold": {1.42, 1.72, 1.00}, "rockwell": {1.16, 1.48, 1.00}, "rod": {1.25, 1.25, 1.00}, "sakkal majalla": {1.10, 1.22, 1.00}, "sarasa gothic": {1.10, 1.33, 1.10}, "segoe print": {1.16, 1.47, 1.00}, "segoe script": {1.10, 1.42, 1.00}, "segoe ui black": {1.12, 1.34, 1.00}, "segoe ui emoji": {1.08, 1.31, 1.00}, "segoe ui historic": {1.08, 1.31, 1.00}, "segoe ui semibold": {1.12, 1.34, 1.00}, "segoe ui semilight": {1.08, 1.31, 1.00}, "segoe ui symbol": {1.08, 1.31, 1.00}, "segoe ui": {1.08, 1.31, 1.00}, "shonar bangla": {1.00, 1.32, 1.00}, "showcard gothic": {1.50, 1.92, 1.00}, "shruti": {0.97, 1.30, 1.00}, "simfang": {1.10, 1.10, 1.10}, "simhei": {1.10, 1.10, 1.10}, "simkai": {1.10, 1.10, 1.10}, "simsun-extb": {1.10, 1.10, 1.10}, "simsun": {1.10, 1.10, 1.10}, "sitka banner": {0.95, 1.28, 1.00}, "sitka display": {1.00, 1.32, 1.00}, "sitka heading": {1.03, 1.36, 1.00}, "sitka small": {1.07, 1.40, 1.00}, "sitka subheading": {1.03, 1.36, 1.00}, "snap itc": {1.32, 1.62, 1.00}, "source han sans": {1.10, 1.33, 1.10}, "source han serif": {1.10, 1.33, 1.10}, "stcaiyun": {1.10, 1.10, 1.10}, "stencil": {1.16, 1.47, 1.00}, "stfangsong": {1.10, 1.90, 1.10}, "stheiti": {1.10, 1.10, 1.10}, "sthupo": {1.10, 1.90, 1.10}, "stkaiti": {1.10, 1.10, 1.10}, "stliti": {1.10, 1.10, 1.10}, "stsong": {1.10, 1.36, 1.10}, "stxingkai": {1.10, 1.36, 1.10}, "stxinwei": {1.10, 1.36, 1.10}, "stzhongsong": {1.10, 1.66, 1.10}, "sylfaen": {1.03, 1.36, 1.00}, "tahoma": {1.17, 1.30, 1.00}, "tempus sans itc": {1.14, 1.46, 1.00}, "times new roman": {1.11, 1.40, 1.00}, "traditional arabic": {0.95, 1.56, 1.00}, "trebuchet ms": {1.10, 1.26, 1.00}, "tunga": {1.15, 1.28, 1.00}, "tw cen mt condensed extra bold": {0.88, 1.22, 1.00}, "tw cen mt condensed": {0.86, 1.18, 1.00}, "tw cen mt": {0.98, 1.32, 1.00}, "ud digi kyokasho n-b": {1.30, 1.36, 1.10}, "ud digi kyokasho n-r": {1.30, 1.36, 1.10}, "ud digi kyokasho nk-b": {1.30, 1.36, 1.10}, "ud digi kyokasho nk-r": {1.30, 1.36, 1.10}, "ud デジタル 教科書体 n": {1.30, 1.36, 1.10}, "ud デジタル 教科書体 nk": {1.30, 1.36, 1.10}, "urdu typesetting": {1.52, 1.52, 1.00}, "utsaah": {1.43, 1.43, 1.00}, "vani": {1.27, 1.30, 1.00}, "verdana": {1.33, 1.44, 1.00}, "vijaya": {1.10, 1.22, 1.00}, "vivaldi": {0.76, 2.12, 1.00}, "vladimir script": {0.82, 1.54, 1.00}, "vrinda": {1.15, 1.28, 1.00}, "wide latin": {1.88, 2.65, 1.00}, "youyuan": {1.20, 1.36, 1.10}, "yu gothic light": {1.03, 1.34, 1.03}, "yu gothic medium": {1.12, 1.41, 1.12}, "yu gothic ui light": {1.03, 1.24, 1.03}, "yu gothic ui semibold": {1.12, 1.31, 1.12}, "yu gothic ui semilight": {1.03, 1.24, 1.03}, "yu gothic ui": {1.12, 1.41, 1.12}, "yu gothic": {1.12, 1.41, 1.12}, "yu mincho": {1.08, 1.48, 1.08}, "굴림": {0.99, 1.31, 1.01}, "굴림체": {1.10, 1.36, 1.20}, "궁서": {0.99, 1.31, 1.01}, "궁서체": {1.10, 1.36, 1.20}, "돋움": {0.99, 1.31, 0.99}, "돋움체": {1.10, 1.10, 1.15}, "맑은 고딕 semilight": {1.07, 1.51, 1.07}, "맑은 고딕": {1.10, 1.33, 1.10}, "바탕": {0.99, 1.31, 1.01}, "바탕체": {1.10, 1.10, 1.20}, "メイリオ": {1.19, 1.42, 1.19}, "仿宋": {1.20, 1.36, 1.40}, "华文中宋": {1.10, 1.66, 1.10}, "华文仿宋": {1.10, 1.90, 1.10}, "华文宋体": {1.10, 1.36, 1.10}, "华文彩云": {1.10, 1.10, 1.10}, "华文新魏": {1.10, 1.36, 1.10}, "华文楷体": {1.10, 1.10, 1.10}, "华文琥珀": {1.10, 1.90, 1.10}, "华文行楷": {1.10, 1.36, 1.10}, "华文隶书": {1.10, 1.10, 1.10}, "华文黑体": {1.10, 1.10, 1.10}, "宋体": {1.10, 1.10, 1.10}, "幼圆": {1.10, 1.36, 1.10}, "微软正黑体 light": {1.10, 1.30, 1.10}, "微软正黑体": {1.16, 1.39, 1.16}, "微软雅黑 light": {1.10, 1.30, 1.10}, "微软雅黑": {1.18, 1.30, 1.18}, "思源宋体": {1.10, 1.33, 1.10}, "思源黑体": {1.10, 1.33, 1.10}, "新宋体": {1.10, 1.10, 1.10}, "新细明体": {1.10, 1.20, 1.10}, "方正姚体": {1.10, 1.36, 1.10}, "方正舒体": {1.10, 1.36, 1.10}, "更纱黑体": {1.10, 1.33, 1.10}, "楷体": {1.20, 1.20, 1.10}, "標楷體": {1.20, 1.20, 1.10}, "游ゴシック light": {1.03, 1.34, 1.03}, "游ゴシック medium": {1.12, 1.41, 1.12}, "游ゴシック ui": {1.12, 1.41, 1.12}, "游ゴシック": {1.12, 1.41, 1.12}, "游明朝": {1.08, 1.48, 1.08}, "等线": {1.05, 1.28, 1.05}, "细明体": {1.10, 1.20, 1.10}, "隶书": {1.10, 1.10, 1.10}, "霞鹜文楷": {1.10, 1.60, 1.10}, "黑体": {1.10, 1.10, 1.10}, "\uff2d\uff33 \uff30ゴシック": {0.99, 1.31, 0.99}, "\uff2d\uff33 ゴシック": {1.10, 1.10, 1.10}, "\uff2d\uff33 明朝": {1.10, 1.10, 1.10}, } // supportedImageTypes defined supported image types. var supportedImageTypes = map[string]string{ ".bmp": ".bmp", ".emf": ".emf", ".emz": ".emz", ".gif": ".gif", ".ico": ".ico", ".jpeg": ".jpeg", ".jpg": ".jpeg", ".png": ".png", ".svg": ".svg", ".tif": ".tiff", ".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz", } // supportedCalcMode defined supported formula calculate mode. var supportedCalcMode = []string{"manual", "auto", "autoNoTable"} // supportedRefMode defined supported formula calculate mode. var supportedRefMode = []string{"A1", "R1C1"} // supportedContentTypes defined supported file format types. var supportedContentTypes = map[string]string{ ".xlam": ContentTypeAddinMacro, ".xlsm": ContentTypeMacro, ".xlsx": ContentTypeSheetML, ".xltm": ContentTypeTemplateMacro, ".xltx": ContentTypeTemplate, } // supportedUnderlineTypes defined supported underline types. var supportedUnderlineTypes = []string{"none", "single", "double"} // supportedVertAlignTypes defined supported vertical align types. var supportedVertAlignTypes = []string{"baseline", "superscript", "subscript"} // supportedDrawingUnderlineTypes defined supported underline types in drawing // markup language. var supportedDrawingUnderlineTypes = []string{ "none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy", "wavyDbl", } // supportedDrawingTextVerticalType defined supported text vertical types in // drawing markup language. var supportedDrawingTextVerticalType = []string{"horz", "vert", "vert270", "wordArtVert", "eaVert", "mongolianVert", "wordArtVertRtl"} // supportedPositioning defined supported positioning types. var supportedPositioning = []string{"absolute", "oneCell", "twoCell"} // supportedPageOrientation defined supported page setup page orientation. var supportedPageOrientation = []string{"portrait", "landscape"} // supportedPageOrder defined supported page setup page order. var supportedPageOrder = []string{"overThenDown", "downThenOver"} // builtInDefinedNames defined built-in defined names are built with a _xlnm prefix. var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm.Criteria", "_xlnm._FilterDatabase", "_xlnm.Extract", "_xlnm.Consolidate_Area", "_xlnm.Database", "_xlnm.Sheet_Title"} const templateDocpropsApp = `0Go Excelize` const templateContentTypes = `` const templateWorkbook = `` const templateStyles = `` const templateSheet = `` const templateWorkbookRels = `` const templateDocpropsCore = `xuri2006-09-16T00:00:00Z2006-09-16T00:00:00Z` const templateRels = `` const templateTheme = `` const templateNamespaceIDMap = ` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:ap="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:op="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:cdr="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" xmlns:comp="http://schemas.openxmlformats.org/drawingml/2006/compatibility" xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xne="http://schemas.microsoft.com/office/excel/2006/main" xmlns:mso="http://schemas.microsoft.com/office/2006/01/customui" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:cppr="http://schemas.microsoft.com/office/2006/coverPageProps" xmlns:cdip="http://schemas.microsoft.com/office/2006/customDocumentInformationPanel" xmlns:ct="http://schemas.microsoft.com/office/2006/metadata/contentType" xmlns:ntns="http://schemas.microsoft.com/office/2006/metadata/customXsn" xmlns:lp="http://schemas.microsoft.com/office/2006/metadata/longProperties" xmlns:ma="http://schemas.microsoft.com/office/2006/metadata/properties/metaAttributes" xmlns:msink="http://schemas.microsoft.com/ink/2010/main" xmlns:c14="http://schemas.microsoft.com/office/drawing/2007/8/2/chart" xmlns:cdr14="http://schemas.microsoft.com/office/drawing/2010/chartDrawing" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" xmlns:pic14="http://schemas.microsoft.com/office/drawing/2010/picture" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:xdr14="http://schemas.microsoft.com/office/excel/2010/spreadsheetDrawing" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:dsp="http://schemas.microsoft.com/office/drawing/2008/diagram" xmlns:mso14="http://schemas.microsoft.com/office/2009/07/customui" xmlns:dgm14="http://schemas.microsoft.com/office/drawing/2010/diagram" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x12ac="http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr4="http://schemas.microsoft.com/office/spreadsheetml/2016/revision4" xmlns:xr5="http://schemas.microsoft.com/office/spreadsheetml/2016/revision5" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr7="http://schemas.microsoft.com/office/spreadsheetml/2016/revision7" xmlns:xr8="http://schemas.microsoft.com/office/spreadsheetml/2016/revision8" xmlns:xr9="http://schemas.microsoft.com/office/spreadsheetml/2016/revision9" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr11="http://schemas.microsoft.com/office/spreadsheetml/2016/revision11" xmlns:xr12="http://schemas.microsoft.com/office/spreadsheetml/2016/revision12" xmlns:xr13="http://schemas.microsoft.com/office/spreadsheetml/2016/revision13" xmlns:xr14="http://schemas.microsoft.com/office/spreadsheetml/2016/revision14" xmlns:xr15="http://schemas.microsoft.com/office/spreadsheetml/2016/revision15" xmlns:x16="http://schemas.microsoft.com/office/spreadsheetml/2014/11/main" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" mc:Ignorable="c14 cdr14 a14 pic14 x14 xdr14 x14ac dsp mso14 dgm14 x15 x12ac x15ac xr xr2 xr3 xr4 xr5 xr6 xr7 xr8 xr9 xr10 xr11 xr12 xr13 xr14 xr15 x15 x16 x16r2 mo mx mv o v" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xr:uid="{00000000-0001-0000-0000-000000000000}">` ================================================ FILE: vml.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "path/filepath" "strconv" "strings" ) // FormControlType is the type of supported form controls. type FormControlType byte // This section defines the currently supported form control types enumeration. const ( FormControlNote FormControlType = iota FormControlButton FormControlOptionButton FormControlSpinButton FormControlCheckBox FormControlGroupBox FormControlLabel FormControlScrollBar ) // HeaderFooterImagePositionType is the type of header and footer image position. type HeaderFooterImagePositionType byte // Worksheet header and footer image position types enumeration. const ( HeaderFooterImagePositionLeft HeaderFooterImagePositionType = iota HeaderFooterImagePositionCenter HeaderFooterImagePositionRight ) // GetComments retrieves all comments in a worksheet by given worksheet name. func (f *File) GetComments(sheet string) ([]Comment, error) { var comments []Comment sheetXMLPath, ok := f.getSheetXMLPath(sheet) if !ok { return comments, ErrSheetNotExist{sheet} } commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath)) if !strings.HasPrefix(commentsXML, "/") { commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..") } commentsXML = strings.TrimPrefix(commentsXML, "/") cmts, err := f.commentsReader(commentsXML) if err != nil { return comments, err } if cmts != nil { for _, cmt := range cmts.CommentList.Comment { comment := Comment{} if cmt.AuthorID < len(cmts.Authors.Author) { comment.Author = cmts.Authors.Author[cmt.AuthorID] } comment.Cell = cmt.Ref comment.AuthorID = cmt.AuthorID if cmt.Text.T != nil { comment.Text += *cmt.Text.T } for _, text := range cmt.Text.R { if text.T != nil { run := RichTextRun{Text: text.T.Val} if text.RPr != nil { run.Font = text.RPr.getFont() } comment.Paragraph = append(comment.Paragraph, run) } } comments = append(comments, comment) } } return comments, nil } // getSheetComments provides the method to get the target comment reference by // given worksheet file path. func (f *File) getSheetComments(sheetFile string) string { rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels") if sheetRels := rels; sheetRels != nil { sheetRels.mu.Lock() defer sheetRels.mu.Unlock() for _, v := range sheetRels.Relationships { if v.Type == SourceRelationshipComments { return v.Target } } } return "" } // AddComment provides the method to add comments in a sheet by giving the // worksheet name, cell reference, and format set (such as author and text). // Note that the maximum author name length is 255 and the max text length is // 32512, and each cell can only have one comment, an error will return if // adding comment on a cell which already exist comment. For example, add a // rich-text comment with a specified comments box size in Sheet1!A5: // // err := f.AddComment("Sheet1", excelize.Comment{ // Cell: "A5", // Author: "Excelize", // Paragraph: []excelize.RichTextRun{ // {Text: "Excelize: ", Font: &excelize.Font{Bold: true}}, // {Text: "This is a comment."}, // }, // Height: 40, // Width: 180, // }) func (f *File) AddComment(sheet string, opts Comment) error { return f.addVMLObject(vmlOptions{ sheet: sheet, Comment: opts, FormControl: FormControl{ Cell: opts.Cell, Type: FormControlNote, Text: opts.Text, Paragraph: opts.Paragraph, Width: opts.Width, Height: opts.Height, }, }) } // DeleteComment provides the method to delete comment in a worksheet by given // worksheet name and cell reference. For example, delete the comment in // Sheet1!$A$30: // // err := f.DeleteComment("Sheet1", "A30") func (f *File) DeleteComment(sheet, cell string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if ws.LegacyDrawing == nil { return err } sheetXMLPath, _ := f.getSheetXMLPath(sheet) commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath)) if !strings.HasPrefix(commentsXML, "/") { commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..") } commentsXML = strings.TrimPrefix(commentsXML, "/") cmts, err := f.commentsReader(commentsXML) if err != nil { return err } if cmts != nil { for i := 0; i < len(cmts.CommentList.Comment); i++ { cmt := cmts.CommentList.Comment[i] if cmt.Ref != cell { continue } if len(cmts.CommentList.Comment) > 1 { cmts.CommentList.Comment = append( cmts.CommentList.Comment[:i], cmts.CommentList.Comment[i+1:]..., ) if idx := inStrSlice(cmts.cells, cell, true); idx != -1 { cmts.cells = append( cmts.cells[:idx], cmts.cells[idx+1:]..., ) } i-- continue } cmts.CommentList.Comment = nil cmts.cells = nil } f.Comments[commentsXML] = cmts } sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID) return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, true) } // deleteFormControl provides the method to delete shape from // xl/drawings/vmlDrawing%d.xml by giving path, cell and shape type. func (f *File) deleteFormControl(sheetRelationshipsDrawingVML, cell string, isComment bool) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } vmlID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml")) drawingVML := strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl") vml := f.VMLDrawing[drawingVML] if vml == nil { vml = &vmlDrawing{ XMLNSv: "urn:schemas-microsoft-com:vml", XMLNSo: "urn:schemas-microsoft-com:office:office", XMLNSx: "urn:schemas-microsoft-com:office:excel", XMLNSmv: "http://macVmlSchemaUri", ShapeLayout: &xlsxShapeLayout{ Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: vmlID}, }, ShapeType: &xlsxShapeType{ Stroke: &xlsxStroke{JoinStyle: "miter"}, VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"}, }, } // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return err } if d != nil { vml.ShapeType.ID = d.ShapeType.ID vml.ShapeType.CoordSize = d.ShapeType.CoordSize vml.ShapeType.Spt = d.ShapeType.Spt vml.ShapeType.Path = d.ShapeType.Path for _, v := range d.Shape { s := xlsxShape{ ID: v.ID, Type: v.Type, Style: v.Style, Button: v.Button, Filled: v.Filled, FillColor: v.FillColor, InsetMode: v.InsetMode, Stroked: v.Stroked, StrokeColor: v.StrokeColor, Val: v.Val, } vml.Shape = append(vml.Shape, s) } } } cond := func(objectType string) bool { if isComment { return objectType == "Note" } return objectType != "Note" } for i, sp := range vml.Shape { var shapeVal decodeShapeVal if err = xml.Unmarshal([]byte(fmt.Sprintf("%s", sp.Val)), &shapeVal); err == nil && cond(shapeVal.ClientData.ObjectType) && shapeVal.ClientData.Anchor != "" { leftCol, topRow, err := extractAnchorCell(shapeVal.ClientData.Anchor) if err != nil { return err } if leftCol == col-1 && topRow == row-1 { vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...) break } } } f.VMLDrawing[drawingVML] = vml return err } // addComment provides a function to create chart as xl/comments%d.xml by // given cell and format sets. func (f *File) addComment(commentsXML string, opts vmlOptions) error { if opts.Author == "" { opts.Author = "Author" } if countUTF16String(opts.Author) > MaxFieldLength { opts.Author = truncateUTF16Units(opts.Author, MaxFieldLength) } cmts, err := f.commentsReader(commentsXML) if err != nil { return err } var authorID int if cmts == nil { cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{opts.Author}}} } if inStrSlice(cmts.cells, opts.Comment.Cell, true) != -1 { return newAddCommentError(opts.Comment.Cell) } if inStrSlice(cmts.Authors.Author, opts.Author, true) == -1 { cmts.Authors.Author = append(cmts.Authors.Author, opts.Author) authorID = len(cmts.Authors.Author) - 1 } defaultFont, err := f.GetDefaultFont() if err != nil { return err } chars, cmt := 0, xlsxComment{ Ref: opts.Comment.Cell, AuthorID: authorID, Text: xlsxText{R: []xlsxR{}}, } if opts.Comment.Text != "" { if countUTF16String(opts.Comment.Text) > TotalCellChars { opts.Comment.Text = truncateUTF16Units(opts.Comment.Text, TotalCellChars) } cmt.Text.T = stringPtr(opts.Comment.Text) chars += countUTF16String(opts.Comment.Text) } for _, run := range opts.Comment.Paragraph { if chars == TotalCellChars { break } if chars+countUTF16String(run.Text) > TotalCellChars { run.Text = truncateUTF16Units(run.Text, TotalCellChars-chars) } chars += countUTF16String(run.Text) r := xlsxR{ RPr: &xlsxRPr{ Sz: &attrValFloat{Val: float64Ptr(9)}, Color: &xlsxColor{ Indexed: 81, }, RFont: &attrValString{Val: stringPtr(defaultFont)}, Family: &attrValInt{Val: intPtr(2)}, }, T: &xlsxT{Val: run.Text, Space: xml.Attr{ Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve", }}, } if run.Font != nil { r.RPr = run.Font.newRpr() } cmt.Text.R = append(cmt.Text.R, r) } cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt) cmts.cells = append(cmts.cells, cmt.Ref) f.Comments[commentsXML] = cmts return err } // countComments provides a function to get comments files count storage in // the folder xl. func (f *File) countComments() int { comments := map[string]struct{}{} f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/comments") { comments[k.(string)] = struct{}{} } return true }) for rel := range f.Comments { if strings.Contains(rel, "xl/comments") { comments[rel] = struct{}{} } } return len(comments) } // commentsReader provides a function to get the pointer to the structure // after deserialization of xl/comments%d.xml. func (f *File) commentsReader(path string) (*xlsxComments, error) { if f.Comments[path] == nil { content, ok := f.Pkg.Load(path) if ok && content != nil { f.Comments[path] = new(xlsxComments) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(f.Comments[path]); err != nil && err != io.EOF { return nil, err } } } if cmts := f.Comments[path]; cmts != nil && len(cmts.cells) == 0 { for idx := 0; idx < len(cmts.CommentList.Comment); idx++ { cmts.cells = append(cmts.cells, cmts.CommentList.Comment[idx].Ref) } f.Comments[path] = cmts } return f.Comments[path], nil } // commentsWriter provides a function to save xl/comments%d.xml after // serialize structure. func (f *File) commentsWriter() { for path, c := range f.Comments { if c != nil { v, _ := xml.Marshal(c) f.saveFileList(path, v) } } } // AddFormControl provides the method to add form control object in a worksheet // by given worksheet name and form control options. Supported form control // type: button, check box, group box, label, option button, scroll bar and // spinner. If set macro for the form control, the workbook extension should be // XLSM or XLTM. Scroll value must be between 0 and 30000. Please note that if a // cell link is set for a checkbox form control, Excelize will not assign a // value to the linked cell when the checkbox is checked. To reflect the // checkbox state, please use the 'SetCellValue' function to manually set the // linked cell's value to true. // // Example 1, add button form control with macro, rich-text, custom button size, // print property on Sheet1!A2, and let the button do not move or size with // cells: // // enable := true // err := f.AddFormControl("Sheet1", excelize.FormControl{ // Cell: "A2", // Type: excelize.FormControlButton, // Macro: "Button1_Click", // Width: 140, // Height: 60, // Text: "Button 1\r\n", // Paragraph: []excelize.RichTextRun{ // { // Font: &excelize.Font{ // Bold: true, // Italic: true, // Underline: "single", // Family: "Times New Roman", // Size: 14, // Color: "777777", // }, // Text: "C1=A1+B1", // }, // }, // Format: excelize.GraphicOptions{ // PrintObject: &enable, // Positioning: "absolute", // }, // }) // // Example 2, add option button form control with checked status and text on // Sheet1!A1: // // err := f.AddFormControl("Sheet1", excelize.FormControl{ // Cell: "A1", // Type: excelize.FormControlOptionButton, // Text: "Option Button 1", // Checked: true, // }) // // Example 3, add spin button form control on Sheet1!B1 to increase or decrease // the value of Sheet1!A1: // // err := f.AddFormControl("Sheet1", excelize.FormControl{ // Cell: "B1", // Type: excelize.FormControlSpinButton, // Width: 15, // Height: 40, // CurrentVal: 7, // MinVal: 5, // MaxVal: 10, // IncChange: 1, // CellLink: "A1", // }) // // Example 4, add horizontally scroll bar form control on Sheet1!A2 to change // the value of Sheet1!A1 by click the scroll arrows or drag the scroll box: // // err := f.AddFormControl("Sheet1", excelize.FormControl{ // Cell: "A2", // Type: excelize.FormControlScrollBar, // Width: 140, // Height: 20, // CurrentVal: 50, // MinVal: 10, // MaxVal: 100, // IncChange: 1, // PageChange: 1, // CellLink: "A1", // Horizontally: true, // }) func (f *File) AddFormControl(sheet string, opts FormControl) error { return f.addVMLObject(vmlOptions{ formCtrl: true, sheet: sheet, FormControl: opts, }) } // DeleteFormControl provides the method to delete form control in a worksheet // by given worksheet name and cell reference. For example, delete the form // control in Sheet1!$A$1: // // err := f.DeleteFormControl("Sheet1", "A1") func (f *File) DeleteFormControl(sheet, cell string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } if ws.LegacyDrawing == nil { return err } sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID) return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, false) } // countVMLDrawing provides a function to get VML drawing files count storage // in the folder xl/drawings. func (f *File) countVMLDrawing() int { drawings := map[string]struct{}{} f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/drawings/vmlDrawing") { drawings[k.(string)] = struct{}{} } return true }) for rel := range f.VMLDrawing { if strings.Contains(rel, "xl/drawings/vmlDrawing") { drawings[rel] = struct{}{} } } return len(drawings) } // decodeVMLDrawingReader provides a function to get the pointer to the // structure after deserialization of xl/drawings/vmlDrawing%d.xml. func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) { if f.DecodeVMLDrawing[path] == nil { c, ok := f.Pkg.Load(path) if ok && c != nil { f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) if err := f.xmlNewDecoder(bytes.NewReader(bytesReplace(namespaceStrictToTransitional(c.([]byte)), []byte("
\r\n"), []byte("

\r\n"), -1))). Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF { return nil, err } } } return f.DecodeVMLDrawing[path], nil } // vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml // after serialize structure. func (f *File) vmlDrawingWriter() { for path, vml := range f.VMLDrawing { if vml != nil { v, _ := xml.Marshal(vml) f.Pkg.Store(path, v) } } } // addVMLObject provides a function to create VML drawing parts and // relationships for comments and form controls. func (f *File) addVMLObject(opts vmlOptions) error { // Read sheet data ws, err := f.workSheetReader(opts.sheet) if err != nil { return err } vmlID := f.countComments() + 1 if opts.formCtrl { if opts.Type > FormControlScrollBar { return ErrParameterInvalid } vmlID = f.countVMLDrawing() + 1 } sheetID := f.getSheetID(opts.sheet) drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" sheetXMLPath, _ := f.getSheetXMLPath(opts.sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" if ws.LegacyDrawing != nil { // The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml. sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.sheet, ws.LegacyDrawing.RID) vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml")) drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl") } else { // Add first VML drawing for given sheet. rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") f.addSheetNameSpace(opts.sheet, SourceRelationship) f.addSheetLegacyDrawing(opts.sheet, rID) } if err = f.addDrawingVML(sheetID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil { return err } if !opts.formCtrl { commentsXML := "xl/comments" + strconv.Itoa(vmlID) + ".xml" if err = f.addComment(commentsXML, opts); err != nil { return err } if sheetXMLPath, ok := f.getSheetXMLPath(opts.sheet); ok && f.getSheetComments(filepath.Base(sheetXMLPath)) == "" { sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml" f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "") } } return f.addContentTypePart(vmlID, "comments") } // prepareFormCtrlOptions provides a function to parse the format settings of // the form control with default value. func prepareFormCtrlOptions(opts *vmlOptions) *vmlOptions { if opts.Format.ScaleX == 0 { opts.Format.ScaleX = 1 } if opts.Format.ScaleY == 0 { opts.Format.ScaleY = 1 } if opts.FormControl.Width == 0 { opts.FormControl.Width = 140 } if opts.FormControl.Height == 0 { opts.FormControl.Height = 60 } return opts } // formCtrlText returns font element in the VML for control form text. func formCtrlText(opts *vmlOptions) []vmlFont { var font []vmlFont if opts.FormControl.Text != "" { font = append(font, vmlFont{Content: opts.FormControl.Text}) } for _, run := range opts.FormControl.Paragraph { fnt := vmlFont{ Content: run.Text + "

\r\n", } if run.Font != nil { fnt.Face = run.Font.Family fnt.Color = run.Font.Color if !strings.HasPrefix(run.Font.Color, "#") { fnt.Color = "#" + fnt.Color } if run.Font.Size != 0 { fnt.Size = uint(run.Font.Size * 20) } if run.Font.Underline == "single" { fnt.Content = "" + fnt.Content + "" } if run.Font.Underline == "double" { fnt.Content = "" + fnt.Content + "" } if run.Font.Italic { fnt.Content = "" + fnt.Content + "" } if run.Font.Bold { fnt.Content = "" + fnt.Content + "" } } font = append(font, fnt) } return font } var formCtrlPresets = map[FormControlType]formCtrlPreset{ FormControlNote: { objectType: "Note", autoFill: "True", filled: "", fillColor: "#FBF6D6", stroked: "", strokeColor: "#EDEAA1", strokeButton: "", fill: &vFill{ Color2: "#FBFE82", Angle: -180, Type: "gradient", Fill: &oFill{Ext: "view", Type: "gradientUnscaled"}, }, textHAlign: "", textVAlign: "", noThreeD: nil, firstButton: nil, shadow: &vShadow{On: "t", Color: "black", Obscured: "t"}, }, FormControlButton: { objectType: "Button", autoFill: "True", filled: "", fillColor: "buttonFace [67]", stroked: "", strokeColor: "windowText [64]", strokeButton: "t", fill: &vFill{ Color2: "buttonFace [67]", Angle: -180, Type: "gradient", Fill: &oFill{Ext: "view", Type: "gradientUnscaled"}, }, textHAlign: "Center", textVAlign: "Center", noThreeD: nil, firstButton: nil, shadow: nil, }, FormControlCheckBox: { objectType: "Checkbox", autoFill: "True", filled: "f", fillColor: "window [65]", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "Center", noThreeD: stringPtr(""), firstButton: nil, shadow: nil, }, FormControlGroupBox: { objectType: "GBox", autoFill: "False", filled: "f", fillColor: "", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "", noThreeD: stringPtr(""), firstButton: nil, shadow: nil, }, FormControlLabel: { objectType: "Label", autoFill: "False", filled: "f", fillColor: "window [65]", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "", noThreeD: nil, firstButton: nil, shadow: nil, }, FormControlOptionButton: { objectType: "Radio", autoFill: "False", filled: "f", fillColor: "window [65]", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "Center", noThreeD: stringPtr(""), firstButton: stringPtr(""), shadow: nil, }, FormControlScrollBar: { objectType: "Scroll", autoFill: "", filled: "", fillColor: "", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "", noThreeD: nil, firstButton: nil, shadow: nil, }, FormControlSpinButton: { objectType: "Spin", autoFill: "False", filled: "", fillColor: "", stroked: "f", strokeColor: "windowText [64]", strokeButton: "", fill: nil, textHAlign: "", textVAlign: "", noThreeD: nil, firstButton: nil, shadow: nil, }, } // addFormCtrl check and add scroll bar or spinner form control by given options. func (sp *encodeShape) addFormCtrl(opts *vmlOptions) error { if opts.Type != FormControlScrollBar && opts.Type != FormControlSpinButton { return nil } if opts.CurrentVal > MaxFormControlValue || opts.MinVal > MaxFormControlValue || opts.MaxVal > MaxFormControlValue || opts.IncChange > MaxFormControlValue || opts.PageChange > MaxFormControlValue { return ErrFormControlValue } if opts.CellLink != "" { if _, _, err := CellNameToCoordinates(opts.CellLink); err != nil { return err } } sp.ClientData.FmlaLink = opts.CellLink sp.ClientData.Val = opts.CurrentVal sp.ClientData.Min = opts.MinVal sp.ClientData.Max = opts.MaxVal sp.ClientData.Inc = opts.IncChange sp.ClientData.Page = opts.PageChange if opts.Type == FormControlScrollBar { if opts.Horizontally { sp.ClientData.Horiz = stringPtr("") } sp.ClientData.Dx = 15 } return nil } // addFormCtrlShape returns a VML shape by given preset and options. func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor string, opts *vmlOptions) (*encodeShape, error) { sp := encodeShape{ Fill: preset.fill, Shadow: preset.shadow, Path: &vPath{ConnectType: "none"}, TextBox: &vTextBox{ Style: "mso-direction-alt:auto", Div: &xlsxDiv{Style: "text-align:left"}, }, ClientData: &xClientData{ ObjectType: preset.objectType, Anchor: anchor, AutoFill: preset.autoFill, Row: intPtr(row - 1), Column: intPtr(col - 1), TextHAlign: preset.textHAlign, TextVAlign: preset.textVAlign, NoThreeD: preset.noThreeD, FirstButton: preset.firstButton, }, } if opts.Format.PrintObject != nil && !*opts.Format.PrintObject { sp.ClientData.PrintObject = "False" } if opts.Format.Positioning != "" { idx := inStrSlice(supportedPositioning, opts.Format.Positioning, true) if idx == -1 { return &sp, newInvalidOptionalValue("Positioning", opts.Format.Positioning, supportedPositioning) } sp.ClientData.MoveWithCells = []*string{stringPtr(""), nil, nil}[idx] sp.ClientData.SizeWithCells = []*string{stringPtr(""), stringPtr(""), nil}[idx] } if opts.Type == FormControlNote { sp.ClientData.MoveWithCells = stringPtr("") sp.ClientData.SizeWithCells = stringPtr("") } if !opts.formCtrl { return &sp, nil } sp.TextBox.Div.Font = formCtrlText(opts) sp.ClientData.FmlaMacro = opts.Macro if (opts.Type == FormControlCheckBox || opts.Type == FormControlOptionButton) && opts.Checked { sp.ClientData.Checked = 1 } if opts.Type == FormControlCheckBox { sp.ClientData.FmlaLink = opts.CellLink } return &sp, sp.addFormCtrl(opts) } // addDrawingVML provides a function to create VML drawing XML as // xl/drawings/vmlDrawing%d.vml by given data ID, XML path and VML options. The // anchor value is a comma-separated list of data written out as: LeftColumn, // LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow, // BottomOffset. func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) error { col, row, err := CellNameToCoordinates(opts.FormControl.Cell) if err != nil { return err } leftOffset, vmlID, vml, preset := 23, 202, f.VMLDrawing[drawingVML], formCtrlPresets[opts.Type] style := "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden" if opts.formCtrl { leftOffset, vmlID = 0, 201 style = "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;mso-wrap-style:tight" } colStart, rowStart, colEnd, rowEnd, _, _, x2, y2 := f.positionObjectPixels(opts.sheet, col, row, int(opts.FormControl.Width), int(opts.FormControl.Height), &opts.Format) anchor := fmt.Sprintf("%d, %d, %d, 0, %d, %d, %d, %d", colStart, leftOffset, rowStart, colEnd, x2, rowEnd, y2) if vml == nil { vml = &vmlDrawing{ XMLNSv: "urn:schemas-microsoft-com:vml", XMLNSo: "urn:schemas-microsoft-com:office:office", XMLNSx: "urn:schemas-microsoft-com:office:excel", XMLNSmv: "http://macVmlSchemaUri", ShapeLayout: &xlsxShapeLayout{ Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID}, }, ShapeType: &xlsxShapeType{ ID: fmt.Sprintf("_x0000_t%d", vmlID), CoordSize: "21600,21600", Spt: 202, Path: "m0,0l0,21600,21600,21600,21600,0xe", Stroke: &xlsxStroke{JoinStyle: "miter"}, VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"}, }, } // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return err } if d != nil { vml.ShapeType.ID = d.ShapeType.ID vml.ShapeType.CoordSize = d.ShapeType.CoordSize vml.ShapeType.Spt = d.ShapeType.Spt vml.ShapeType.Path = d.ShapeType.Path for _, v := range d.Shape { s := xlsxShape{ ID: v.ID, Type: v.Type, Style: v.Style, Button: v.Button, Filled: v.Filled, FillColor: v.FillColor, InsetMode: v.InsetMode, Stroked: v.Stroked, StrokeColor: v.StrokeColor, Val: v.Val, } vml.Shape = append(vml.Shape, s) } } } sp, err := f.addFormCtrlShape(preset, col, row, anchor, opts) if err != nil { return err } s, _ := xml.Marshal(sp) shape := xlsxShape{ ID: "_x0000_s1025", Type: fmt.Sprintf("#_x0000_t%d", vmlID), Style: style, Button: preset.strokeButton, Filled: preset.filled, FillColor: preset.fillColor, Stroked: preset.stroked, StrokeColor: preset.strokeColor, Val: string(s[13 : len(s)-14]), } vml.Shape = append(vml.Shape, shape) f.VMLDrawing[drawingVML] = vml return err } // GetFormControls retrieves all form controls in a worksheet by a given // worksheet name. Note that, this function does not support getting the width // and height of the form controls currently. func (f *File) GetFormControls(sheet string) ([]FormControl, error) { var formControls []FormControl // Read sheet data ws, err := f.workSheetReader(sheet) if err != nil { return formControls, err } if ws.LegacyDrawing == nil { return formControls, err } target := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID) drawingVML := strings.ReplaceAll(target, "..", "xl") vml := f.VMLDrawing[drawingVML] if vml == nil { // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return formControls, err } for _, sp := range d.Shape { if sp.Type != "#_x0000_t201" { continue } formControl, err := extractFormControl(sp.Val) if err != nil { return formControls, err } if formControl.Type == FormControlNote || formControl.Cell == "" { continue } formControls = append(formControls, formControl) } return formControls, err } for _, sp := range vml.Shape { if sp.Type != "#_x0000_t201" { continue } formControl, err := extractFormControl(sp.Val) if err != nil { return formControls, err } if formControl.Type == FormControlNote || formControl.Cell == "" { continue } formControls = append(formControls, formControl) } return formControls, err } // extractFormControl provides a function to extract form controls for a // worksheets by given client data. func extractFormControl(clientData string) (FormControl, error) { var ( err error formControl FormControl shapeVal decodeShapeVal ) if err = xml.Unmarshal([]byte(fmt.Sprintf("%s", clientData)), &shapeVal); err != nil { return formControl, err } for formCtrlType, preset := range formCtrlPresets { if shapeVal.ClientData.ObjectType == preset.objectType && shapeVal.ClientData.Anchor != "" { formControl.Paragraph = extractVMLFont(shapeVal.TextBox.Div.Font) if len(formControl.Paragraph) > 0 && formControl.Paragraph[0].Font == nil { formControl.Text = formControl.Paragraph[0].Text formControl.Paragraph = formControl.Paragraph[1:] } formControl.Type = formCtrlType col, row, err := extractAnchorCell(shapeVal.ClientData.Anchor) if err != nil { return formControl, err } if formControl.Cell, err = CoordinatesToCellName(col+1, row+1); err != nil { return formControl, err } formControl.Macro = shapeVal.ClientData.FmlaMacro formControl.Checked = shapeVal.ClientData.Checked != 0 formControl.CellLink = shapeVal.ClientData.FmlaLink formControl.CurrentVal = shapeVal.ClientData.Val formControl.MinVal = shapeVal.ClientData.Min formControl.MaxVal = shapeVal.ClientData.Max formControl.IncChange = shapeVal.ClientData.Inc formControl.PageChange = shapeVal.ClientData.Page formControl.Horizontally = shapeVal.ClientData.Horiz != nil } } return formControl, err } // extractAnchorCell extract left-top cell coordinates from given VML anchor // comma-separated list values. func extractAnchorCell(anchor string) (int, int, error) { var ( leftCol, topRow int err error pos = strings.Split(anchor, ",") ) if len(pos) != 8 { return leftCol, topRow, ErrParameterInvalid } leftCol, err = strconv.Atoi(strings.TrimSpace(pos[0])) if err != nil { return leftCol, topRow, ErrColumnNumber } topRow, err = strconv.Atoi(strings.TrimSpace(pos[2])) return leftCol, topRow, err } // extractVMLFont extract rich-text and font format from given VML font element. func extractVMLFont(font []decodeVMLFont) []RichTextRun { var runs []RichTextRun extractU := func(u *decodeVMLFontU, run *RichTextRun) { if u == nil { return } run.Text += u.Val if run.Font == nil { run.Font = &Font{} } run.Font.Underline = "single" if u.Class == "font1" { run.Font.Underline = "double" } } extractI := func(i *decodeVMLFontI, run *RichTextRun) { if i == nil { return } extractU(i.U, run) run.Text += i.Val if run.Font == nil { run.Font = &Font{} } run.Font.Italic = true } extractB := func(b *decodeVMLFontB, run *RichTextRun) { if b == nil { return } extractI(b.I, run) run.Text += b.Val if run.Font == nil { run.Font = &Font{} } run.Font.Bold = true } for _, fnt := range font { var run RichTextRun extractB(fnt.B, &run) extractI(fnt.I, &run) extractU(fnt.U, &run) run.Text += fnt.Val if fnt.Face != "" || fnt.Size > 0 || fnt.Color != "" { if run.Font == nil { run.Font = &Font{} } run.Font.Family = fnt.Face run.Font.Size = float64(fnt.Size / 20) run.Font.Color = fnt.Color } runs = append(runs, run) } return runs } // AddHeaderFooterImage provides a mechanism to set the graphics that can be // referenced in the header and footer definitions via &G, supported image // types: EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. // // The extension should be provided with a "." in front, e.g. ".png". // The width and height should have units in them, e.g. "100pt". func (f *File) AddHeaderFooterImage(sheet string, opts *HeaderFooterImageOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } ext, ok := supportedImageTypes[strings.ToLower(opts.Extension)] if !ok { return ErrImgExt } sheetID := f.getSheetID(sheet) vmlID := f.countVMLDrawing() + 1 drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" if ws.LegacyDrawingHF != nil { // The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml. sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawingHF.RID) vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml")) drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl") } else { // Add first VML drawing for given sheet. rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") f.addSheetNameSpace(sheet, SourceRelationship) f.addSheetLegacyDrawingHF(sheet, rID) } shapeID := map[HeaderFooterImagePositionType]string{ HeaderFooterImagePositionLeft: "L", HeaderFooterImagePositionCenter: "C", HeaderFooterImagePositionRight: "R", }[opts.Position] + map[bool]string{false: "H", true: "F"}[opts.IsFooter] + map[bool]string{false: "", true: "FIRST"}[opts.FirstPage] vml := f.VMLDrawing[drawingVML] if vml == nil { vml = &vmlDrawing{ XMLNSv: "urn:schemas-microsoft-com:vml", XMLNSo: "urn:schemas-microsoft-com:office:office", XMLNSx: "urn:schemas-microsoft-com:office:excel", ShapeLayout: &xlsxShapeLayout{ Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID}, }, ShapeType: &xlsxShapeType{ ID: "_x0000_t75", CoordSize: "21600,21600", Spt: 75, PreferRelative: "t", Path: "m@4@5l@4@11@9@11@9@5xe", Filled: "f", Stroked: "f", Stroke: &xlsxStroke{JoinStyle: "miter"}, VFormulas: &vFormulas{ Formulas: []vFormula{ {Equation: "if lineDrawn pixelLineWidth 0"}, {Equation: "sum @0 1 0"}, {Equation: "sum 0 0 @1"}, {Equation: "prod @2 1 2"}, {Equation: "prod @3 21600 pixelWidth"}, {Equation: "prod @3 21600 pixelHeight"}, {Equation: "sum @0 0 1"}, {Equation: "prod @6 1 2"}, {Equation: "prod @7 21600 pixelWidth"}, {Equation: "sum @8 21600 0"}, {Equation: "prod @7 21600 pixelHeight"}, {Equation: "sum @10 21600 0"}, }, }, VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"}, Lock: &oLock{Ext: "edit", AspectRatio: "t"}, }, } // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return err } if d != nil { vml.ShapeType.ID = d.ShapeType.ID vml.ShapeType.CoordSize = d.ShapeType.CoordSize vml.ShapeType.Spt = d.ShapeType.Spt vml.ShapeType.PreferRelative = d.ShapeType.PreferRelative vml.ShapeType.Path = d.ShapeType.Path vml.ShapeType.Filled = d.ShapeType.Filled vml.ShapeType.Stroked = d.ShapeType.Stroked for _, v := range d.Shape { s := xlsxShape{ ID: v.ID, SpID: v.SpID, Type: v.Type, Style: v.Style, Val: v.Val, } vml.Shape = append(vml.Shape, s) } } } for idx, shape := range vml.Shape { if shape.ID == shapeID { vml.Shape = append(vml.Shape[:idx], vml.Shape[idx+1:]...) } } style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", opts.Width, opts.Height) drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels" mediaStr := ".." + strings.TrimPrefix(f.addMedia(opts.File, ext), "xl") imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "") shape := xlsxShape{ ID: shapeID, SpID: "_x0000_s1025", Type: "#_x0000_t75", Style: style, } sp, _ := xml.Marshal(encodeShape{ ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)}, Lock: &oLock{Ext: "edit", Rotation: "t"}, }) shape.Val = string(sp[13 : len(sp)-14]) vml.Shape = append(vml.Shape, shape) f.VMLDrawing[drawingVML] = vml if err := f.setContentTypePartImageExtensions(); err != nil { return err } return f.setContentTypePartVMLExtensions() } ================================================ FILE: vmlDrawing.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // vmlDrawing directly maps the root element in the file // xl/drawings/vmlDrawing%d.vml. type vmlDrawing struct { XMLName xml.Name `xml:"xml"` XMLNSv string `xml:"xmlns:v,attr"` XMLNSo string `xml:"xmlns:o,attr"` XMLNSx string `xml:"xmlns:x,attr"` XMLNSmv string `xml:"xmlns:mv,attr,omitempty"` ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"` ShapeType *xlsxShapeType `xml:"v:shapetype"` Shape []xlsxShape `xml:"v:shape"` } // xlsxShapeLayout directly maps the shapelayout element. This element contains // child elements that store information used in the editing and layout of // shapes. type xlsxShapeLayout struct { Ext string `xml:"v:ext,attr"` IDmap *xlsxIDmap `xml:"o:idmap"` } // xlsxIDmap directly maps the idmap element. type xlsxIDmap struct { Ext string `xml:"v:ext,attr"` Data int `xml:"data,attr"` } // xlsxShape directly maps the shape element. type xlsxShape struct { XMLName xml.Name `xml:"v:shape"` ID string `xml:"id,attr"` SpID string `xml:"o:spid,attr,omitempty"` Type string `xml:"type,attr"` Style string `xml:"style,attr"` Button string `xml:"o:button,attr,omitempty"` Filled string `xml:"filled,attr,omitempty"` FillColor string `xml:"fillcolor,attr,omitempty"` InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"` Stroked string `xml:"stroked,attr,omitempty"` StrokeColor string `xml:"strokecolor,attr,omitempty"` Val string `xml:",innerxml"` } // xlsxShapeType directly maps the shapetype element. type xlsxShapeType struct { ID string `xml:"id,attr"` CoordSize string `xml:"coordsize,attr"` Spt int `xml:"o:spt,attr"` PreferRelative string `xml:"o:preferrelative,attr,omitempty"` Path string `xml:"path,attr"` Filled string `xml:"filled,attr,omitempty"` Stroked string `xml:"stroked,attr,omitempty"` Stroke *xlsxStroke `xml:"v:stroke"` VFormulas *vFormulas `xml:"v:formulas"` VPath *vPath `xml:"v:path"` Lock *oLock `xml:"o:lock"` } // xlsxStroke directly maps the stroke element. type xlsxStroke struct { JoinStyle string `xml:"joinstyle,attr"` } // vPath directly maps the v:path element. type vPath struct { ExtrusionOK string `xml:"o:extrusionok,attr,omitempty"` GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"` ConnectType string `xml:"o:connecttype,attr"` } // oLock directly maps the o:lock element. type oLock struct { Ext string `xml:"v:ext,attr"` Rotation string `xml:"rotation,attr,omitempty"` AspectRatio string `xml:"aspectratio,attr,omitempty"` } // vFormulas directly maps to the v:formulas element type vFormulas struct { Formulas []vFormula `xml:"v:f"` } // vFormula directly maps to the v:f element type vFormula struct { Equation string `xml:"eqn,attr"` } // vFill directly maps the v:fill element. This element must be defined within a // Shape element. type vFill struct { Angle int `xml:"angle,attr,omitempty"` Color2 string `xml:"color2,attr"` Type string `xml:"type,attr,omitempty"` Fill *oFill `xml:"o:fill"` } // oFill directly maps the o:fill element. type oFill struct { Ext string `xml:"v:ext,attr"` Type string `xml:"type,attr,omitempty"` } // vShadow directly maps the v:shadow element. This element must be defined // within a Shape element. In addition, the On attribute must be set to True. type vShadow struct { On string `xml:"on,attr"` Color string `xml:"color,attr,omitempty"` Obscured string `xml:"obscured,attr"` } // vTextBox directly maps the v:textbox element. This element must be defined // within a Shape element. type vTextBox struct { Style string `xml:"style,attr"` Div *xlsxDiv `xml:"div"` } // vImageData directly maps the v:imagedata element. This element must be // defined within a Shape element. type vImageData struct { RelID string `xml:"o:relid,attr"` Title string `xml:"o:title,attr,omitempty"` } // xlsxDiv directly maps the div element. type xlsxDiv struct { Style string `xml:"style,attr"` Font []vmlFont `xml:"font"` } type vmlFont struct { Face string `xml:"face,attr,omitempty"` Size uint `xml:"size,attr,omitempty"` Color string `xml:"color,attr,omitempty"` Content string `xml:",innerxml"` } // xClientData (Attached Object Data) directly maps the x:ClientData element. // This element specifies data associated with objects attached to a // spreadsheet. While this element might contain any of the child elements // below, only certain combinations are meaningful. The ObjectType attribute // determines the kind of object the element represents and which subset of // child elements is appropriate. Relevant groups are identified for each child // element. type xClientData struct { ObjectType string `xml:"ObjectType,attr"` MoveWithCells *string `xml:"x:MoveWithCells"` SizeWithCells *string `xml:"x:SizeWithCells"` Anchor string `xml:"x:Anchor"` Locked string `xml:"x:Locked,omitempty"` PrintObject string `xml:"x:PrintObject,omitempty"` AutoFill string `xml:"x:AutoFill,omitempty"` FmlaMacro string `xml:"x:FmlaMacro,omitempty"` TextHAlign string `xml:"x:TextHAlign,omitempty"` TextVAlign string `xml:"x:TextVAlign,omitempty"` Row *int `xml:"x:Row"` Column *int `xml:"x:Column"` Checked int `xml:"x:Checked,omitempty"` FmlaLink string `xml:"x:FmlaLink,omitempty"` NoThreeD *string `xml:"x:NoThreeD"` FirstButton *string `xml:"x:FirstButton"` Val uint `xml:"x:Val,omitempty"` Min uint `xml:"x:Min,omitempty"` Max uint `xml:"x:Max,omitempty"` Inc uint `xml:"x:Inc,omitempty"` Page uint `xml:"x:Page,omitempty"` Horiz *string `xml:"x:Horiz"` Dx uint `xml:"x:Dx,omitempty"` } // decodeVmlDrawing defines the structure used to parse the file // xl/drawings/vmlDrawing%d.vml. type decodeVmlDrawing struct { ShapeType decodeShapeType `xml:"urn:schemas-microsoft-com:vml shapetype"` Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"` } // decodeShapeType defines the structure used to parse the shapetype element in // the file xl/drawings/vmlDrawing%d.vml. type decodeShapeType struct { ID string `xml:"id,attr"` CoordSize string `xml:"coordsize,attr"` Spt int `xml:"spt,attr"` PreferRelative string `xml:"preferrelative,attr,omitempty"` Path string `xml:"path,attr"` Filled string `xml:"filled,attr,omitempty"` Stroked string `xml:"stroked,attr,omitempty"` } // decodeShape defines the structure used to parse the particular shape element. type decodeShape struct { ID string `xml:"id,attr"` SpID string `xml:"spid,attr,omitempty"` Type string `xml:"type,attr"` Style string `xml:"style,attr"` Button string `xml:"button,attr,omitempty"` Filled string `xml:"filled,attr,omitempty"` FillColor string `xml:"fillcolor,attr,omitempty"` InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"` Stroked string `xml:"stroked,attr,omitempty"` StrokeColor string `xml:"strokecolor,attr,omitempty"` Val string `xml:",innerxml"` } // decodeShapeVal defines the structure used to parse the sub-element of the // shape in the file xl/drawings/vmlDrawing%d.vml. type decodeShapeVal struct { TextBox decodeVMLTextBox `xml:"textbox"` ClientData decodeVMLClientData `xml:"ClientData"` } // decodeVMLFontU defines the structure used to parse the u element in the VML. type decodeVMLFontU struct { Class string `xml:"class,attr"` Val string `xml:",chardata"` } // decodeVMLFontI defines the structure used to parse the i element in the VML. type decodeVMLFontI struct { U *decodeVMLFontU `xml:"u"` Val string `xml:",chardata"` } // decodeVMLFontB defines the structure used to parse the b element in the VML. type decodeVMLFontB struct { I *decodeVMLFontI `xml:"i"` U *decodeVMLFontU `xml:"u"` Val string `xml:",chardata"` } // decodeVMLFont defines the structure used to parse the font element in the VML. type decodeVMLFont struct { Face string `xml:"face,attr,omitempty"` Size uint `xml:"size,attr,omitempty"` Color string `xml:"color,attr,omitempty"` B *decodeVMLFontB `xml:"b"` I *decodeVMLFontI `xml:"i"` U *decodeVMLFontU `xml:"u"` Val string `xml:",chardata"` } // decodeVMLDiv defines the structure used to parse the div element in the VML. type decodeVMLDiv struct { Font []decodeVMLFont `xml:"font"` } // decodeVMLTextBox defines the structure used to parse the v:textbox element in // the file xl/drawings/vmlDrawing%d.vml. type decodeVMLTextBox struct { Div decodeVMLDiv `xml:"div"` } // decodeVMLClientData defines the structure used to parse the x:ClientData // element in the file xl/drawings/vmlDrawing%d.vml. type decodeVMLClientData struct { ObjectType string `xml:"ObjectType,attr"` Anchor string FmlaMacro string Column *int Row *int Checked int FmlaLink string Val uint Min uint Max uint Inc uint Page uint Horiz *string } // encodeShape defines the structure used to re-serialization shape element. type encodeShape struct { Fill *vFill `xml:"v:fill"` Shadow *vShadow `xml:"v:shadow"` Path *vPath `xml:"v:path"` TextBox *vTextBox `xml:"v:textbox"` ImageData *vImageData `xml:"v:imagedata"` ClientData *xClientData `xml:"x:ClientData"` Lock *oLock `xml:"o:lock"` } // formCtrlPreset defines the structure used to form control presets. type formCtrlPreset struct { autoFill string fill *vFill fillColor string filled string firstButton *string noThreeD *string objectType string shadow *vShadow strokeButton string strokeColor string stroked string textHAlign string textVAlign string } // vmlOptions defines the structure used to internal comments and form controls. type vmlOptions struct { formCtrl bool sheet string Comment FormControl } // FormControl directly maps the form controls information. type FormControl struct { Cell string Macro string Width uint Height uint Checked bool CurrentVal uint MinVal uint MaxVal uint IncChange uint PageChange uint Horizontally bool CellLink string Text string Paragraph []RichTextRun Type FormControlType Format GraphicOptions } // HeaderFooterImageOptions defines the settings for an image to be accessible // from the worksheet header and footer options. type HeaderFooterImageOptions struct { Position HeaderFooterImagePositionType File []byte IsFooter bool FirstPage bool Extension string Width string Height string } ================================================ FILE: vml_test.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "fmt" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestAddComment(t *testing.T) { f, err := prepareTestBook1() if !assert.NoError(t, err) { t.FailNow() } s := strings.Repeat("c", TotalCellChars+1) assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A30", Author: s, Text: s, Paragraph: []RichTextRun{{Text: s}, {Text: s}}})) assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "B7", Author: "Excelize", Text: s[:TotalCellChars-1], Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}})) // Test add comment on a cell which already exists a comment assert.Equal(t, f.AddComment("Sheet2", Comment{Cell: "B7", Author: s, Text: s}), newAddCommentError("B7")) // Test add comment on not exists worksheet assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist") // Test add comment on with illegal cell reference assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}})) comments, err := f.GetComments("Sheet1") assert.NoError(t, err) assert.Len(t, comments, 2) comments, err = f.GetComments("Sheet2") assert.NoError(t, err) assert.Len(t, comments, 1) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`Excelize: Excelize: `)) comments, err = f.GetComments("Sheet1") assert.NoError(t, err) assert.Len(t, comments, 2) comments, err = f.GetComments("Sheet2") assert.NoError(t, err) assert.Len(t, comments, 1) comments, err = NewFile().GetComments("Sheet1") assert.NoError(t, err) assert.Len(t, comments, 0) // Test add comments with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.AddComment("Sheet:1", Comment{Cell: "A1", Author: "Excelize", Text: "This is a comment."})) // Test add comments with unsupported charset f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) _, err = f.GetComments("Sheet2") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test add comments with unsupported charset f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8") // Test add comments with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8") // Test get comments on not exists worksheet comments, err = f.GetComments("SheetN") assert.Len(t, comments, 0) assert.EqualError(t, err, "sheet SheetN does not exist") } func TestDeleteComment(t *testing.T) { f, err := prepareTestBook1() if !assert.NoError(t, err) { t.FailNow() } assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A40", Text: "Excelize: This is a comment1."})) assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A41", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment2."}}})) assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C41", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment3."}}})) assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C42", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment4."}}})) assert.NoError(t, f.DeleteComment("Sheet2", "A40")) comments, err := f.GetComments("Sheet2") assert.NoError(t, err) assert.Len(t, comments, 3) comments, err = NewFile().GetComments("Sheet1") assert.NoError(t, err) assert.Len(t, comments, 0) // Test delete comment with invalid sheet name assert.Equal(t, ErrSheetNameInvalid, f.DeleteComment("Sheet:1", "A1")) // Test delete all comments in a worksheet assert.NoError(t, f.DeleteComment("Sheet2", "A41")) assert.NoError(t, f.DeleteComment("Sheet2", "C41")) assert.NoError(t, f.DeleteComment("Sheet2", "C42")) comments, err = f.GetComments("Sheet2") assert.NoError(t, err) assert.EqualValues(t, 0, len(comments)) // Test delete comment on not exists worksheet assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist") // Test delete comment with worksheet part f.Pkg.Delete("xl/worksheets/sheet1.xml") assert.NoError(t, f.DeleteComment("Sheet1", "A22")) f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) assert.EqualError(t, f.DeleteComment("Sheet2", "A41"), "XML syntax error on line 1: invalid UTF-8") f = NewFile() // Test delete comment on a no comments worksheet assert.NoError(t, f.DeleteComment("Sheet1", "A1")) } func TestDecodeVMLDrawingReader(t *testing.T) { f := NewFile() path := "xl/drawings/vmlDrawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) _, err := f.decodeVMLDrawingReader(path) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCommentsReader(t *testing.T) { f := NewFile() // Test read comments with unsupported charset path := "xl/comments1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) _, err := f.commentsReader(path) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCountComments(t *testing.T) { f := NewFile() f.Comments["xl/comments1.xml"] = nil assert.Equal(t, f.countComments(), 1) } func TestAddDrawingVML(t *testing.T) { // Test addDrawingVML with illegal cell reference f := NewFile() assert.Equal(t, f.addDrawingVML(0, "", &vmlOptions{FormControl: FormControl{Cell: "*"}}), newCellNameToCoordinatesError("*", newInvalidCellNameError("*"))) f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", &vmlOptions{sheet: "Sheet1", FormControl: FormControl{Cell: "A1"}}), "XML syntax error on line 1: invalid UTF-8") } func TestFormControl(t *testing.T) { f := NewFile() formControls := []FormControl{ { Cell: "D1", Type: FormControlButton, Macro: "Button1_Click", }, { Cell: "A1", Type: FormControlButton, Macro: "Button1_Click", Width: 140, Height: 60, Text: "Button 1\n", Paragraph: []RichTextRun{ { Font: &Font{ Bold: true, Italic: true, Underline: "single", Family: "Times New Roman", Size: 14, Color: "777777", }, Text: "C1=A1+B1", }, }, Format: GraphicOptions{PrintObject: boolPtr(true), Positioning: "absolute"}, }, { Cell: "A5", Type: FormControlCheckBox, Text: "Check Box 1", Checked: true, Format: GraphicOptions{ PrintObject: boolPtr(false), Positioning: "oneCell", }, }, { Cell: "A6", Type: FormControlCheckBox, Text: "Check Box 2", CellLink: "C5", Format: GraphicOptions{Positioning: "twoCell"}, }, { Cell: "A7", Type: FormControlOptionButton, Text: "Option Button 1", Checked: true, }, { Cell: "A8", Type: FormControlOptionButton, Text: "Option Button 2", }, { Cell: "D3", Type: FormControlGroupBox, Text: "Group Box 1", Width: 140, Height: 60, }, { Cell: "A9", Type: FormControlLabel, Text: "Label 1", Width: 140, }, { Cell: "C5", Type: FormControlSpinButton, Width: 40, Height: 60, CurrentVal: 7, MinVal: 5, MaxVal: 10, IncChange: 1, CellLink: "C2", }, { Cell: "D7", Type: FormControlScrollBar, Width: 140, Height: 20, CurrentVal: 50, MinVal: 10, MaxVal: 100, IncChange: 1, PageChange: 1, Horizontally: true, CellLink: "C3", }, { Cell: "G1", Type: FormControlScrollBar, Width: 20, Height: 140, CurrentVal: 50, MinVal: 1000, MaxVal: 100, IncChange: 1, PageChange: 1, CellLink: "C4", }, } for _, formCtrl := range formControls { assert.NoError(t, f.AddFormControl("Sheet1", formCtrl)) } // Test get from controls result, err := f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, result, 11) for i, formCtrl := range formControls { assert.Equal(t, formCtrl.Type, result[i].Type) assert.Equal(t, formCtrl.Cell, result[i].Cell) assert.Equal(t, formCtrl.Macro, result[i].Macro) assert.Equal(t, formCtrl.Checked, result[i].Checked) assert.Equal(t, formCtrl.CurrentVal, result[i].CurrentVal) assert.Equal(t, formCtrl.MinVal, result[i].MinVal) assert.Equal(t, formCtrl.MaxVal, result[i].MaxVal) assert.Equal(t, formCtrl.IncChange, result[i].IncChange) assert.Equal(t, formCtrl.Horizontally, result[i].Horizontally) assert.Equal(t, formCtrl.CellLink, result[i].CellLink) assert.Equal(t, formCtrl.Text, result[i].Text) assert.Equal(t, len(formCtrl.Paragraph), len(result[i].Paragraph)) } assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")})) file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin")) assert.NoError(t, err) assert.NoError(t, f.AddVBAProject(file)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddFormControl.xlsm"))) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) assert.NoError(t, err) // Test get from controls before add form controls result, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, result, 11) // Test add from control to a worksheet which already contains form controls assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ Cell: "D4", Type: FormControlButton, Macro: "Button1_Click", Paragraph: []RichTextRun{{Font: &Font{Underline: "double"}, Text: "Button 2"}}, })) // Test get from controls after add form controls result, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, result, 12) // Test add unsupported form control assert.Equal(t, f.AddFormControl("Sheet1", FormControl{ Cell: "A1", Type: 0x37, Macro: "Button1_Click", }), ErrParameterInvalid) // Test add form control on not exists worksheet assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddFormControl("SheetN", FormControl{ Cell: "A1", Type: FormControlButton, Macro: "Button1_Click", })) // Test add form control with invalid positioning types assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning), f.AddFormControl("Sheet1", FormControl{ Cell: "A1", Type: FormControlButton, Format: GraphicOptions{Positioning: "x"}, })) // Test add spin form control with illegal cell link reference assert.Equal(t, f.AddFormControl("Sheet1", FormControl{ Cell: "C5", Type: FormControlSpinButton, CellLink: "*", }), newCellNameToCoordinatesError("*", newInvalidCellNameError("*"))) // Test add spin form control with invalid scroll value assert.Equal(t, f.AddFormControl("Sheet1", FormControl{ Cell: "C5", Type: FormControlSpinButton, CurrentVal: MaxFormControlValue + 1, }), ErrFormControlValue) assert.NoError(t, f.Close()) // Test delete form control f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) assert.NoError(t, err) assert.NoError(t, f.DeleteFormControl("Sheet1", "D1")) assert.NoError(t, f.DeleteFormControl("Sheet1", "A1")) // Test get from controls after delete form controls result, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, result, 9) // Test delete form control on not exists worksheet assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.DeleteFormControl("SheetN", "A1")) // Test delete form control with illegal cell link reference assert.Equal(t, f.DeleteFormControl("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteFormControl.xlsm"))) assert.NoError(t, f.Close()) // Test delete form control with expected element f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) assert.NoError(t, err) f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) assert.Error(t, f.DeleteFormControl("Sheet1", "A1"), "XML syntax error on line 1: invalid UTF-8") // Test delete form controls with invalid shape anchor f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: "0"}}, } assert.Equal(t, ErrParameterInvalid, f.DeleteFormControl("Sheet1", "A1")) assert.NoError(t, f.Close()) // Test delete form control on a worksheet without form control f = NewFile() assert.NoError(t, f.DeleteFormControl("Sheet1", "A1")) // Test get form controls on a worksheet without form control _, err = f.GetFormControls("Sheet1") assert.NoError(t, err) // Test get form controls on not exists worksheet _, err = f.GetFormControls("SheetN") assert.Equal(t, ErrSheetNotExist{"SheetN"}, err) // Test get form controls with unsupported charset VML drawing f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) assert.NoError(t, err) f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) _, err = f.GetFormControls("Sheet1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test get form controls with unsupported shape type f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "_x0000_t202"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, formControls, 0) // Test get form controls with bold font format f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: "
Text
0,0,0,0,0,0,0,0"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.True(t, formControls[0].Paragraph[0].Font.Bold) // Test get form controls with italic font format f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: "
Text
0,0,0,0,0,0,0,0"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.True(t, formControls[0].Paragraph[0].Font.Italic) // Test get form controls with font format f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: "
Text
0,0,0,0,0,0,0,0"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Equal(t, "Calibri", formControls[0].Paragraph[0].Font.Family) assert.Equal(t, 14.0, formControls[0].Paragraph[0].Font.Size) assert.Equal(t, "#777777", formControls[0].Paragraph[0].Font.Color) // Test get form controls with italic font format f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: "
Text
0,0,0,0,0,0,0,0"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.True(t, formControls[0].Paragraph[0].Font.Italic) // Test get form controls with invalid column number f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d,0,0,0,0,0,0,0", MaxColumns)}}, } formControls, err = f.GetFormControls("Sheet1") assert.Equal(t, err, ErrColumnNumber) assert.Len(t, formControls, 0) // Test get form controls with comment (Note) shape type f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ Shape: []decodeShape{{Type: "#_x0000_t201", Val: ""}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, formControls, 0) // Test get form controls with unsupported shape type f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ Shape: []xlsxShape{{Type: "_x0000_t202"}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, formControls, 0) // Test get form controls with invalid column number f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ Shape: []xlsxShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d,0,0,0,0,0,0,0", MaxColumns)}}, } formControls, err = f.GetFormControls("Sheet1") assert.Equal(t, err, ErrColumnNumber) assert.Len(t, formControls, 0) // Test get form controls with invalid shape anchor f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ Shape: []xlsxShape{{Type: "#_x0000_t201", Val: "x,0,0,0,0,0,0,0"}}, } formControls, err = f.GetFormControls("Sheet1") assert.Equal(t, ErrColumnNumber, err) assert.Len(t, formControls, 0) // Test get form controls with comment (Note) shape type f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ Shape: []xlsxShape{{Type: "#_x0000_t201", Val: ""}}, } formControls, err = f.GetFormControls("Sheet1") assert.NoError(t, err) assert.Len(t, formControls, 0) assert.NoError(t, f.Close()) } func TestExtractFormControl(t *testing.T) { // Test extract form control with unsupported charset _, err := extractFormControl(string(MacintoshCyrillicCharset)) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestAddHeaderFooterImage(t *testing.T) { f, sheet, wb := NewFile(), "Sheet1", filepath.Join("test", "TestAddHeaderFooterImage.xlsx") headerFooterOptions := HeaderFooterOptions{ DifferentFirst: true, OddHeader: "&L&GExcelize&C&G&R&G", OddFooter: "&L&GExcelize&C&G&R&G", FirstHeader: "&L&GExcelize&C&G&R&G", FirstFooter: "&L&GExcelize&C&G&R&G", } assert.NoError(t, f.SetHeaderFooter(sheet, &headerFooterOptions)) assert.NoError(t, f.SetSheetView(sheet, -1, &ViewOptions{View: stringPtr("pageLayout")})) images := map[string][]byte{ ".wmf": nil, ".tif": nil, ".png": nil, ".jpg": nil, ".gif": nil, ".emz": nil, ".emf": nil, } for ext := range images { img, err := os.ReadFile(filepath.Join("test", "images", "excel"+ext)) assert.NoError(t, err) images[ext] = img } for _, opt := range []struct { position HeaderFooterImagePositionType file []byte isFooter bool firstPage bool ext string }{ {position: HeaderFooterImagePositionLeft, file: images[".tif"], firstPage: true, ext: ".tif"}, {position: HeaderFooterImagePositionCenter, file: images[".gif"], firstPage: true, ext: ".gif"}, {position: HeaderFooterImagePositionRight, file: images[".png"], firstPage: true, ext: ".png"}, {position: HeaderFooterImagePositionLeft, file: images[".emf"], isFooter: true, firstPage: true, ext: ".emf"}, {position: HeaderFooterImagePositionCenter, file: images[".wmf"], isFooter: true, firstPage: true, ext: ".wmf"}, {position: HeaderFooterImagePositionRight, file: images[".emz"], isFooter: true, firstPage: true, ext: ".emz"}, {position: HeaderFooterImagePositionLeft, file: images[".png"], ext: ".png"}, {position: HeaderFooterImagePositionCenter, file: images[".png"], ext: ".png"}, {position: HeaderFooterImagePositionRight, file: images[".png"], ext: ".png"}, {position: HeaderFooterImagePositionLeft, file: images[".tif"], isFooter: true, ext: ".tif"}, {position: HeaderFooterImagePositionCenter, file: images[".tif"], isFooter: true, ext: ".tif"}, {position: HeaderFooterImagePositionRight, file: images[".tif"], isFooter: true, ext: ".tif"}, } { assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{ Position: opt.position, File: opt.file, IsFooter: opt.isFooter, FirstPage: opt.firstPage, Extension: opt.ext, Width: "50pt", Height: "32pt", })) } assert.NoError(t, f.SetCellValue(sheet, "A1", "Example")) // Test add header footer image with not exist sheet assert.EqualError(t, f.AddHeaderFooterImage("SheetN", nil), "sheet SheetN does not exist") // Test add header footer image with unsupported file type assert.Equal(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{ Extension: "jpg", }), ErrImgExt) assert.NoError(t, f.SaveAs(wb)) assert.NoError(t, f.Close()) // Test change already exist header image with the different image f, err := OpenFile(wb) assert.NoError(t, err) assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{ File: images[".jpg"], FirstPage: true, Extension: ".jpg", Width: "50pt", Height: "32pt", })) assert.NoError(t, f.Save()) assert.NoError(t, f.Close()) // Test add header image with unsupported charset VML drawing f, err = OpenFile(wb) assert.NoError(t, err) f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{ File: images[".jpg"], Extension: ".jpg", Width: "50pt", Height: "32pt", }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) // Test set legacy drawing header/footer with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{ Extension: ".png", File: images[".png"], Width: "50pt", Height: "32pt", }), "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } ================================================ FILE: workbook.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "bytes" "encoding/xml" "io" "path/filepath" "reflect" "strconv" "strings" ) // SetWorkbookProps provides a function to sets workbook properties. func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error { if opts == nil { return nil } wb, err := f.workbookReader() if err != nil { return err } if wb.WorkbookPr == nil { wb.WorkbookPr = new(xlsxWorkbookPr) } setNoPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"}, reflect.ValueOf(*opts), reflect.ValueOf(wb.WorkbookPr).Elem()) return err } // GetWorkbookProps provides a function to gets workbook properties. func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) { var opts WorkbookPropsOptions wb, err := f.workbookReader() if err != nil { return opts, err } if wb.WorkbookPr == nil { return opts, err } setPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"}, reflect.ValueOf(*wb.WorkbookPr), reflect.ValueOf(&opts).Elem()) return opts, err } // SetCalcProps provides a function to sets calculation properties. Optional // value of "CalcMode" property is: "manual", "auto" or "autoNoTable". Optional // value of "RefMode" property is: "A1" or "R1C1". func (f *File) SetCalcProps(opts *CalcPropsOptions) error { if opts == nil { return nil } wb, err := f.workbookReader() if err != nil { return err } if wb.CalcPr == nil { wb.CalcPr = new(xlsxCalcPr) } if opts.CalcMode != nil && inStrSlice(supportedCalcMode, *opts.CalcMode, true) == -1 { return newInvalidOptionalValue("CalcMode", *opts.CalcMode, supportedCalcMode) } if opts.RefMode != nil && inStrSlice(supportedRefMode, *opts.RefMode, true) == -1 { return newInvalidOptionalValue("RefMode", *opts.RefMode, supportedRefMode) } setNoPtrFieldsVal([]string{ "CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate", "IterateDelta", "CalcMode", "RefMode", }, reflect.ValueOf(*opts), reflect.ValueOf(wb.CalcPr).Elem()) if opts.CalcID != nil { wb.CalcPr.CalcID = int(*opts.CalcID) } if opts.ConcurrentManualCount != nil { wb.CalcPr.ConcurrentManualCount = int(*opts.ConcurrentManualCount) } if opts.IterateCount != nil { wb.CalcPr.IterateCount = int(*opts.IterateCount) } wb.CalcPr.ConcurrentCalc = opts.ConcurrentCalc return err } // GetCalcProps provides a function to gets calculation properties. func (f *File) GetCalcProps() (CalcPropsOptions, error) { var opts CalcPropsOptions wb, err := f.workbookReader() if err != nil { return opts, err } if wb.CalcPr == nil { return opts, err } setPtrFieldsVal([]string{ "CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate", "IterateDelta", "CalcMode", "RefMode", }, reflect.ValueOf(*wb.CalcPr), reflect.ValueOf(&opts).Elem()) opts.CalcID = uintPtr(uint(wb.CalcPr.CalcID)) opts.ConcurrentManualCount = uintPtr(uint(wb.CalcPr.ConcurrentManualCount)) opts.IterateCount = uintPtr(uint(wb.CalcPr.IterateCount)) opts.ConcurrentCalc = wb.CalcPr.ConcurrentCalc return opts, err } // ProtectWorkbook provides a function to prevent other users from viewing // hidden worksheets, adding, moving, deleting, or hiding worksheets, and // renaming worksheets in a workbook. The optional field AlgorithmName // specified hash algorithm, support XOR, MD4, MD5, SHA-1, SHA2-56, SHA-384, // and SHA-512 currently, if no hash algorithm specified, will be using the XOR // algorithm as default. The generated workbook only works on Microsoft Office // 2007 and later. For example, protect workbook with protection settings: // // err := f.ProtectWorkbook(&excelize.WorkbookProtectionOptions{ // Password: "password", // LockStructure: true, // }) func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error { wb, err := f.workbookReader() if err != nil { return err } if wb.WorkbookProtection == nil { wb.WorkbookProtection = new(xlsxWorkbookProtection) } if opts == nil { opts = &WorkbookProtectionOptions{} } wb.WorkbookProtection = &xlsxWorkbookProtection{ LockStructure: opts.LockStructure, LockWindows: opts.LockWindows, } if opts.Password != "" { if opts.AlgorithmName == "" { opts.AlgorithmName = "SHA-512" } hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(workbookProtectionSpinCount)) if err != nil { return err } wb.WorkbookProtection.WorkbookAlgorithmName = opts.AlgorithmName wb.WorkbookProtection.WorkbookSaltValue = saltValue wb.WorkbookProtection.WorkbookHashValue = hashValue wb.WorkbookProtection.WorkbookSpinCount = int(workbookProtectionSpinCount) } return err } // UnprotectWorkbook provides a function to remove protection for workbook, // specified the optional password parameter to remove workbook protection with // password verification. func (f *File) UnprotectWorkbook(password ...string) error { wb, err := f.workbookReader() if err != nil { return err } // password verification if len(password) > 0 { if wb.WorkbookProtection == nil { return ErrUnprotectWorkbook } if wb.WorkbookProtection.WorkbookAlgorithmName != "" { // check with given salt value hashValue, _, err := genISOPasswdHash(password[0], wb.WorkbookProtection.WorkbookAlgorithmName, wb.WorkbookProtection.WorkbookSaltValue, wb.WorkbookProtection.WorkbookSpinCount) if err != nil { return err } if wb.WorkbookProtection.WorkbookHashValue != hashValue { return ErrUnprotectWorkbookPassword } } } wb.WorkbookProtection = nil return err } // setWorkbook update workbook property of the spreadsheet. Maximum 31 // characters are allowed in sheet title. func (f *File) setWorkbook(name string, sheetID, rid int) { wb, _ := f.workbookReader() wb.Sheets.Sheet = append(wb.Sheets.Sheet, xlsxSheet{ Name: name, SheetID: sheetID, ID: "rId" + strconv.Itoa(rid), }) } // getWorkbookPath provides a function to get the path of the workbook.xml in // the spreadsheet. func (f *File) getWorkbookPath() (path string) { if rels, _ := f.relsReader(defaultXMLPathRels); rels != nil { rels.mu.Lock() defer rels.mu.Unlock() for _, rel := range rels.Relationships { if rel.Type == SourceRelationshipOfficeDocument { path = strings.TrimPrefix(rel.Target, "/") return } } } return } // getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels // in the spreadsheet. func (f *File) getWorkbookRelsPath() (path string) { wbPath := f.getWorkbookPath() wbDir := filepath.Dir(wbPath) if wbDir == "." { path = "_rels/" + filepath.Base(wbPath) + ".rels" return } path = strings.TrimPrefix(filepath.Dir(wbPath)+"/_rels/"+filepath.Base(wbPath)+".rels", "/") return } // deleteWorkbookRels provides a function to delete relationships in // xl/_rels/workbook.xml.rels by given type and target. func (f *File) deleteWorkbookRels(relType, relTarget string) (string, error) { var rID string rels, err := f.relsReader(f.getWorkbookRelsPath()) if err != nil { return rID, err } if rels == nil { rels = &xlsxRelationships{} } for k, v := range rels.Relationships { if v.Type == relType && v.Target == relTarget { rID = v.ID rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...) } } return rID, err } // workbookReader provides a function to get the pointer to the workbook.xml // structure after deserialization. func (f *File) workbookReader() (*xlsxWorkbook, error) { var err error if f.WorkBook == nil { wbPath := f.getWorkbookPath() f.WorkBook = new(xlsxWorkbook) if attrs, ok := f.xmlAttr.Load(wbPath); !ok { d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))) if attrs == nil { attrs = []xml.Attr{} } attrs = append(attrs.([]xml.Attr), getRootElement(d)...) f.xmlAttr.Store(wbPath, attrs) f.addNameSpaces(wbPath, SourceRelationship) } if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))). Decode(f.WorkBook); err != nil && err != io.EOF { return f.WorkBook, err } } return f.WorkBook, err } // workBookWriter provides a function to save workbook.xml after serialize // structure. func (f *File) workBookWriter() { if f.WorkBook != nil { if f.WorkBook.DecodeAlternateContent != nil { f.WorkBook.AlternateContent = &xlsxAlternateContent{ Content: f.WorkBook.DecodeAlternateContent.Content, XMLNSMC: SourceRelationshipCompatibility.Value, } } f.WorkBook.DecodeAlternateContent = nil output, _ := xml.Marshal(f.WorkBook) f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output))) } } // setContentTypePartRelsExtensions provides a function to set the content type // for relationship parts and the Main Document part. func (f *File) setContentTypePartRelsExtensions() error { var rels bool content, err := f.contentTypesReader() if err != nil { return err } for _, v := range content.Defaults { if v.Extension == "rels" { rels = true } } if !rels { content.Defaults = append(content.Defaults, xlsxDefault{ Extension: "rels", ContentType: ContentTypeRelationships, }) } return err } // setContentTypePartImageExtensions provides a function to set the content type // for relationship parts and the Main Document part. func (f *File) setContentTypePartImageExtensions() error { imageTypes := map[string]string{ "bmp": "image/", "ico": "image/x-", "jpeg": "image/", "png": "image/", "gif": "image/", "svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-", } content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for _, file := range content.Defaults { delete(imageTypes, file.Extension) } for extension, prefix := range imageTypes { content.Defaults = append(content.Defaults, xlsxDefault{ Extension: extension, ContentType: prefix + extension, }) } return err } // setContentTypePartVMLExtensions provides a function to set the content type // for relationship parts and the Main Document part. func (f *File) setContentTypePartVMLExtensions() error { var vml bool content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for _, v := range content.Defaults { if v.Extension == "vml" { vml = true } } if !vml { content.Defaults = append(content.Defaults, xlsxDefault{ Extension: "vml", ContentType: ContentTypeVML, }) } return err } // addContentTypePart provides a function to add content type part relationships // in the file [Content_Types].xml by given index and content type. func (f *File) addContentTypePart(index int, contentType string) error { setContentType := map[string]func() error{ "comments": f.setContentTypePartVMLExtensions, "drawings": f.setContentTypePartImageExtensions, } partNames := map[string]string{ "chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml", "chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml", "comments": "/xl/comments" + strconv.Itoa(index) + ".xml", "customProperties": "/docProps/custom.xml", "drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml", "table": "/xl/tables/table" + strconv.Itoa(index) + ".xml", "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml", "pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml", "sharedStrings": "/xl/sharedStrings.xml", "slicer": "/xl/slicers/slicer" + strconv.Itoa(index) + ".xml", "slicerCache": "/xl/slicerCaches/slicerCache" + strconv.Itoa(index) + ".xml", } contentTypes := map[string]string{ "chart": ContentTypeDrawingML, "chartsheet": ContentTypeSpreadSheetMLChartsheet, "comments": ContentTypeSpreadSheetMLComments, "customProperties": ContentTypeCustomProperties, "drawings": ContentTypeDrawing, "table": ContentTypeSpreadSheetMLTable, "pivotTable": ContentTypeSpreadSheetMLPivotTable, "pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition, "sharedStrings": ContentTypeSpreadSheetMLSharedStrings, "slicer": ContentTypeSlicer, "slicerCache": ContentTypeSlicerCache, } s, ok := setContentType[contentType] if ok { if err := s(); err != nil { return err } } content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for _, v := range content.Overrides { if v.PartName == partNames[contentType] { return err } } content.Overrides = append(content.Overrides, xlsxOverride{ PartName: partNames[contentType], ContentType: contentTypes[contentType], }) return f.setContentTypePartRelsExtensions() } // removeContentTypesPart provides a function to remove relationships by given // content type and part name in the file [Content_Types].xml. func (f *File) removeContentTypesPart(contentType, partName string) error { if !strings.HasPrefix(partName, "/") { partName = "/xl/" + partName } content, err := f.contentTypesReader() if err != nil { return err } content.mu.Lock() defer content.mu.Unlock() for k, v := range content.Overrides { if v.PartName == partName && v.ContentType == contentType { content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) } } return err } ================================================ FILE: workbook_test.go ================================================ package excelize import ( "testing" "github.com/stretchr/testify/assert" ) func TestWorkbookProps(t *testing.T) { f := NewFile() assert.NoError(t, f.SetWorkbookProps(nil)) wb, err := f.workbookReader() assert.NoError(t, err) wb.WorkbookPr = nil expected := WorkbookPropsOptions{ Date1904: boolPtr(true), FilterPrivacy: boolPtr(true), CodeName: stringPtr("code"), } assert.NoError(t, f.SetWorkbookProps(&expected)) opts, err := f.GetWorkbookProps() assert.NoError(t, err) assert.Equal(t, expected, opts) wb.WorkbookPr = nil opts, err = f.GetWorkbookProps() assert.NoError(t, err) assert.Equal(t, WorkbookPropsOptions{}, opts) // Test set workbook properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8") // Test get workbook properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err = f.GetWorkbookProps() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCalcProps(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCalcProps(nil)) wb, err := f.workbookReader() assert.NoError(t, err) wb.CalcPr = nil expected := CalcPropsOptions{ FullCalcOnLoad: boolPtr(true), CalcID: uintPtr(122211), ConcurrentManualCount: uintPtr(5), IterateCount: uintPtr(10), ConcurrentCalc: boolPtr(true), } assert.NoError(t, f.SetCalcProps(&expected)) opts, err := f.GetCalcProps() assert.NoError(t, err) assert.Equal(t, expected, opts) wb.CalcPr = nil opts, err = f.GetCalcProps() assert.NoError(t, err) assert.Equal(t, CalcPropsOptions{}, opts) // Test set calculation properties with unsupported optional value assert.Equal(t, newInvalidOptionalValue("CalcMode", "AUTO", supportedCalcMode), f.SetCalcProps(&CalcPropsOptions{CalcMode: stringPtr("AUTO")})) assert.Equal(t, newInvalidOptionalValue("RefMode", "a1", supportedRefMode), f.SetCalcProps(&CalcPropsOptions{RefMode: stringPtr("a1")})) // Test set calculation properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCalcProps(&expected), "XML syntax error on line 1: invalid UTF-8") // Test get calculation properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err = f.GetCalcProps() assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestDeleteWorkbookRels(t *testing.T) { f := NewFile() // Test delete pivot table without worksheet relationships f.Relationships.Delete("xl/_rels/workbook.xml.rels") f.Pkg.Delete("xl/_rels/workbook.xml.rels") rID, err := f.deleteWorkbookRels("", "") assert.Empty(t, rID) assert.NoError(t, err) } ================================================ FILE: xmlApp.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // AppProperties directly maps the document application properties. type AppProperties struct { Application string ScaleCrop bool DocSecurity int Company string LinksUpToDate bool HyperlinksChanged bool AppVersion string } // xlsxProperties specifies to an OOXML document properties such as the // template used, the number of pages and words, and the application name and // version. type xlsxProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"` Vt string `xml:"xmlns:vt,attr"` Template string `xml:",omitempty"` Manager string `xml:",omitempty"` Company string `xml:",omitempty"` Pages int `xml:",omitempty"` Words int `xml:",omitempty"` Characters int `xml:",omitempty"` PresentationFormat string `xml:",omitempty"` Lines int `xml:",omitempty"` Paragraphs int `xml:",omitempty"` Slides int `xml:",omitempty"` Notes int `xml:",omitempty"` TotalTime int `xml:",omitempty"` HiddenSlides int `xml:",omitempty"` MMClips int `xml:",omitempty"` ScaleCrop bool `xml:",omitempty"` HeadingPairs *xlsxVectorVariant TitlesOfParts *xlsxVectorLpstr LinksUpToDate bool `xml:",omitempty"` CharactersWithSpaces int `xml:",omitempty"` SharedDoc bool `xml:",omitempty"` HyperlinkBase string `xml:",omitempty"` HLinks *xlsxVectorVariant HyperlinksChanged bool `xml:",omitempty"` DigSig *xlsxDigSig Application string `xml:",omitempty"` AppVersion string `xml:",omitempty"` DocSecurity int `xml:",omitempty"` } // xlsxVectorVariant specifies the set of hyperlinks that were in this // document when last saved. type xlsxVectorVariant struct { Content string `xml:",innerxml"` } type xlsxVectorLpstr struct { Content string `xml:",innerxml"` } // xlsxDigSig contains the signature of a digitally signed document. type xlsxDigSig struct { Content string `xml:",innerxml"` } ================================================ FILE: xmlCalcChain.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxCalcChain directly maps the calcChain element. This element represents // the root of the calculation chain. type xlsxCalcChain struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"` C []xlsxCalcChainC `xml:"c"` } // xlsxCalcChainC directly maps the c element. // // Attributes | Attributes // --------------------------+---------------------------------------------------------- // a (Array) | A Boolean flag indicating whether the cell's formula // | is an array formula. True if this cell's formula is // | an array formula, false otherwise. If there is a // | conflict between this attribute and the t attribute // | of the f element (§18.3.1.40), the t attribute takes // | precedence. The possible values for this attribute // | are defined by the W3C XML Schema boolean datatype. // | // i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is // | omitted, it is assumed to be the same as the i value // | of the previous cell.The possible values for this // | attribute are defined by the W3C XML Schema int datatype. // | // l (New Dependency Level) | A Boolean flag indicating that the cell's formula // | starts a new dependency level. True if the formula // | starts a new dependency level, false otherwise. // | Starting a new dependency level means that all // | concurrent calculations, and child calculations, shall // | be completed - and the cells have new values - before // | the calc chain can continue. In other words, this // | dependency level might depend on levels that came before // | it, and any later dependency levels might depend on // | this level; but not later dependency levels can have // | any calculations started until this dependency level // | completes.The possible values for this attribute are // | defined by the W3C XML Schema boolean datatype. // | // r (Cell Reference) | An A-1 style reference to a cell.The possible values // | for this attribute are defined by the ST_CellRef // | simple type (§18.18.7). // | // s (Child Chain) | A Boolean flag indicating whether the cell's formula // | is on a child chain. True if this cell is part of a // | child chain, false otherwise. If this is omitted, it // | is assumed to be the same as the s value of the // | previous cell .A child chain is a list of calculations // | that occur which depend on the parent to the chain. // | There shall not be cross dependencies between child // | chains. Child chains are not the same as dependency // | levels - a child chain and its parent are all on the // | same dependency level. Child chains are series of // | calculations that can be independently farmed out to // | other threads or processors.The possible values for // | this attribute is defined by the W3C XML Schema // | boolean datatype. // | // t (New Thread) | A Boolean flag indicating whether the cell's formula // | starts a new thread. True if the cell's formula starts // | a new thread, false otherwise.The possible values for // | this attribute is defined by the W3C XML Schema // | boolean datatype. type xlsxCalcChainC struct { R string `xml:"r,attr"` I int `xml:"i,attr,omitempty"` L bool `xml:"l,attr,omitempty"` S bool `xml:"s,attr,omitempty"` T bool `xml:"t,attr,omitempty"` A bool `xml:"a,attr,omitempty"` } // xlsxVolTypes maps the volatileDependencies part provides a cache of data that // supports Real Time Data (RTD) and CUBE functions in the workbook. type xlsxVolTypes struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main volTypes"` VolType []xlsxVolType `xml:"volType"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxVolType represents dependency information for a specific type of external // data server. type xlsxVolType struct { Type string `xml:"type,attr"` Main []xlsxVolMain `xml:"main"` } // xlsxVolMain represents dependency information for all topics within a // volatile dependency type that share the same first string or function // argument. type xlsxVolMain struct { First string `xml:"first,attr"` Tp []xlsxVolTopic `xml:"tp"` } // xlsxVolTopic represents dependency information for all topics within a // volatile dependency type that share the same first string or argument. type xlsxVolTopic struct { T string `xml:"t,attr,omitempty"` V string `xml:"v"` Stp []string `xml:"stp"` Tr []xlsxVolTopicRef `xml:"tr"` } // xlsxVolTopicRef represents the reference to a cell that depends on this // topic. Each topic can have one or more cells dependencies. type xlsxVolTopicRef struct { R string `xml:"r,attr"` S int `xml:"s,attr"` } ================================================ FILE: xmlChart.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxChartSpace directly maps the chartSpace element. The chart namespace in // DrawingML is for representing visualizations of numeric data with column // charts, pie charts, scatter charts, or other types of charts. type xlsxChartSpace struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"` XMLNSa string `xml:"xmlns:a,attr"` Date1904 *attrValBool `xml:"date1904"` Lang *attrValString `xml:"lang"` RoundedCorners *attrValBool `xml:"roundedCorners"` Chart cChart `xml:"chart"` SpPr *cSpPr `xml:"spPr"` TxPr *cTxPr `xml:"txPr"` PrintSettings *cPrintSettings `xml:"printSettings"` } // cThicknessSpPr directly maps the element that specifies the thickness of // the walls or floor as a percentage of the largest dimension of the plot // volume and SpPr element. type cThicknessSpPr struct { Thickness *attrValInt `xml:"thickness"` SpPr *cSpPr `xml:"spPr"` } // cChart (Chart) directly maps the chart element. This element specifies a // title. type cChart struct { Title *cTitle `xml:"title"` AutoTitleDeleted *cAutoTitleDeleted `xml:"autoTitleDeleted"` View3D *cView3D `xml:"view3D"` Floor *cThicknessSpPr `xml:"floor"` SideWall *cThicknessSpPr `xml:"sideWall"` BackWall *cThicknessSpPr `xml:"backWall"` PlotArea *cPlotArea `xml:"plotArea"` Legend *cLegend `xml:"legend"` PlotVisOnly *attrValBool `xml:"plotVisOnly"` DispBlanksAs *attrValString `xml:"dispBlanksAs"` ShowDLblsOverMax *attrValBool `xml:"showDLblsOverMax"` } // cTitle (Title) directly maps the title element. This element specifies a // title. type cTitle struct { Tx cTx `xml:"tx,omitempty"` Layout string `xml:"layout,omitempty"` Overlay *attrValBool `xml:"overlay"` SpPr cSpPr `xml:"spPr,omitempty"` TxPr cTxPr `xml:"txPr,omitempty"` } // cTx (Chart Text) directly maps the tx element. This element specifies text // to use on a chart, including rich text formatting. type cTx struct { StrRef *cStrRef `xml:"strRef"` Rich *cRich `xml:"rich,omitempty"` } // cRich (Rich Text) directly maps the rich element. This element contains a // string with rich text formatting. type cRich struct { BodyPr aBodyPr `xml:"a:bodyPr,omitempty"` LstStyle string `xml:"a:lstStyle,omitempty"` P []aP `xml:"a:p"` } // aBodyPr (Body Properties) directly maps the a:bodyPr element. This element // defines the body properties for the text body within a shape. type aBodyPr struct { Anchor string `xml:"anchor,attr,omitempty"` AnchorCtr bool `xml:"anchorCtr,attr"` Rot int `xml:"rot,attr"` BIns float64 `xml:"bIns,attr,omitempty"` CompatLnSpc bool `xml:"compatLnSpc,attr,omitempty"` ForceAA bool `xml:"forceAA,attr,omitempty"` FromWordArt bool `xml:"fromWordArt,attr,omitempty"` HorzOverflow string `xml:"horzOverflow,attr,omitempty"` LIns float64 `xml:"lIns,attr,omitempty"` NumCol int `xml:"numCol,attr,omitempty"` RIns float64 `xml:"rIns,attr,omitempty"` RtlCol bool `xml:"rtlCol,attr,omitempty"` SpcCol int `xml:"spcCol,attr,omitempty"` SpcFirstLastPara bool `xml:"spcFirstLastPara,attr"` TIns float64 `xml:"tIns,attr,omitempty"` Upright bool `xml:"upright,attr,omitempty"` Vert string `xml:"vert,attr,omitempty"` VertOverflow string `xml:"vertOverflow,attr,omitempty"` Wrap string `xml:"wrap,attr,omitempty"` } // aP (Paragraph) directly maps the a:p element. This element specifies a // paragraph of content in the document. type aP struct { PPr *aPPr `xml:"a:pPr"` R *aR `xml:"a:r"` EndParaRPr *aEndParaRPr `xml:"a:endParaRPr"` } // aPPr (Paragraph Properties) directly maps the a:pPr element. This element // specifies a set of paragraph properties which shall be applied to the // contents of the parent paragraph after all style/numbering/table properties // have been applied to the text. These properties are defined as direct // formatting, since they are directly applied to the paragraph and supersede // any formatting from styles. type aPPr struct { DefRPr aRPr `xml:"a:defRPr"` } // aSrgbClr (RGB Color Model - Hex Variant) specifies a color using the red, // green, blue RGB color model. Red, green, and blue is expressed as sequence of // hex digits, RRGGBB. A perceptual gamma of 2.2 is used. type aSrgbClr struct { Val *string `xml:"val,attr"` Tint *attrValInt `xml:"a:tint"` Shade *attrValInt `xml:"a:shade"` Comp *attrValInt `xml:"a:comp"` Inv *attrValInt `xml:"a:inv"` Gray *attrValInt `xml:"a:gray"` Alpha *attrValInt `xml:"a:alpha"` AlphaOff *attrValInt `xml:"a:alphaOff"` AlphaMod *attrValInt `xml:"a:alphaMod"` Hue *attrValInt `xml:"a:hue"` HueOff *attrValInt `xml:"a:hueOff"` HueMod *attrValInt `xml:"a:hueMod"` Sat *attrValInt `xml:"a:sat"` SatOff *attrValInt `xml:"a:satOff"` SatMod *attrValInt `xml:"a:satMod"` Lum *attrValInt `xml:"a:lum"` LumOff *attrValInt `xml:"a:lumOff"` LumMod *attrValInt `xml:"a:lumMod"` Red *attrValInt `xml:"a:red"` RedOff *attrValInt `xml:"a:redOff"` RedMod *attrValInt `xml:"a:redMod"` Green *attrValInt `xml:"a:green"` GreenOff *attrValInt `xml:"a:greenOff"` GreenMod *attrValInt `xml:"a:greenMod"` Blue *attrValInt `xml:"a:blue"` BlueOff *attrValInt `xml:"a:blueOff"` BlueMod *attrValInt `xml:"a:blueMod"` Gamma *attrValInt `xml:"a:gamma"` InvGamma *attrValInt `xml:"a:invGamma"` } // aSolidFill (Solid Fill) directly maps the solidFill element. This element // specifies a solid color fill. The shape is filled entirely with the specified // color. type aSolidFill struct { SchemeClr *aSchemeClr `xml:"a:schemeClr"` SrgbClr *aSrgbClr `xml:"a:srgbClr"` } // aSchemeClr (Scheme Color) directly maps the a:schemeClr element. This // element specifies a color bound to a user's theme. As with all elements which // define a color, it is possible to apply a list of color transforms to the // base color defined. type aSchemeClr struct { Val string `xml:"val,attr,omitempty"` LumMod *attrValInt `xml:"a:lumMod"` LumOff *attrValInt `xml:"a:lumOff"` } // attrValInt directly maps the val element with integer data type as an // attribute. type attrValInt struct { Val *int `xml:"val,attr"` } // attrValFloat directly maps the val element with float64 data type as an // attribute. type attrValFloat struct { Val *float64 `xml:"val,attr"` } // attrValBool directly maps the val element with boolean data type as an // attribute. type attrValBool struct { Val *bool `xml:"val,attr"` } // attrValString directly maps the val element with string data type as an // attribute. type attrValString struct { Val *string `xml:"val,attr"` } type xlsxCTTextFont struct { Typeface string `xml:"typeface,attr"` Panose string `xml:"panose,attr,omitempty"` PitchFamily string `xml:"pitchFamily,attr,omitempty"` Charset string `xml:"Charset,attr,omitempty"` } // aR directly maps the a:r element. type aR struct { RPr aRPr `xml:"a:rPr,omitempty"` T string `xml:"a:t,omitempty"` } // aRPr (Run Properties) directly maps the rPr element. This element // specifies a set of run properties which shall be applied to the contents of // the parent run after all style formatting has been applied to the text. These // properties are defined as direct formatting, since they are directly applied // to the run and supersede any formatting from styles. type aRPr struct { AltLang string `xml:"altLang,attr,omitempty"` B bool `xml:"b,attr"` Baseline int `xml:"baseline,attr"` Bmk string `xml:"bmk,attr,omitempty"` Cap string `xml:"cap,attr,omitempty"` Dirty bool `xml:"dirty,attr,omitempty"` Err bool `xml:"err,attr,omitempty"` I bool `xml:"i,attr"` Kern int `xml:"kern,attr"` Kumimoji bool `xml:"kumimoji,attr,omitempty"` Lang string `xml:"lang,attr,omitempty"` NoProof bool `xml:"noProof,attr,omitempty"` NormalizeH bool `xml:"normalizeH,attr,omitempty"` SmtClean bool `xml:"smtClean,attr,omitempty"` SmtID uint64 `xml:"smtId,attr,omitempty"` Spc int `xml:"spc,attr"` Strike string `xml:"strike,attr,omitempty"` Sz float64 `xml:"sz,attr,omitempty"` U string `xml:"u,attr,omitempty"` SolidFill *aSolidFill `xml:"a:solidFill"` Latin *xlsxCTTextFont `xml:"a:latin"` Ea *xlsxCTTextFont `xml:"a:ea"` Cs *xlsxCTTextFont `xml:"a:cs"` } // cDTable (Data Table) directly maps the dTable element. type cDTable struct { ShowHorzBorder *attrValBool `xml:"showHorzBorder"` ShowVertBorder *attrValBool `xml:"showVertBorder"` ShowOutline *attrValBool `xml:"showOutline"` ShowKeys *attrValBool `xml:"showKeys"` SpPr *cSpPr `xml:"spPr"` TxPr *cTxPr `xml:"txPr"` ExtLst *xlsxExtLst `xml:"extLst"` } // cSpPr (Shape Properties) directly maps the spPr element. This element // specifies the visual shape properties that can be applied to a shape. These // properties include the shape fill, outline, geometry, effects, and 3D // orientation. type cSpPr struct { NoFill *string `xml:"a:noFill"` SolidFill *aSolidFill `xml:"a:solidFill"` Ln *aLn `xml:"a:ln"` Sp3D *aSp3D `xml:"a:sp3d"` EffectLst *string `xml:"a:effectLst"` } // aSp3D (3-D Shape Properties) directly maps the a:sp3d element. This element // defines the 3D properties associated with a particular shape in DrawingML. // The 3D properties which can be applied to a shape are top and bottom bevels, // a contour and an extrusion. type aSp3D struct { ContourW int `xml:"contourW,attr"` ContourClr *aContourClr `xml:"a:contourClr"` } // aContourClr (Contour Color) directly maps the a:contourClr element. This // element defines the color for the contour on a shape. The contour of a shape // is a solid filled line which surrounds the outer edges of the shape. type aContourClr struct { SchemeClr *aSchemeClr `xml:"a:schemeClr"` } // aLn (Outline) directly maps the a:ln element. This element specifies an // outline style that can be applied to a number of different objects such as // shapes and text. The line allows for the specifying of many different types // of outlines including even line dashes and bevels. type aLn struct { Algn string `xml:"algn,attr,omitempty"` Cap string `xml:"cap,attr,omitempty"` Cmpd string `xml:"cmpd,attr,omitempty"` W int `xml:"w,attr,omitempty"` NoFill *attrValString `xml:"a:noFill"` Round string `xml:"a:round,omitempty"` SolidFill *aSolidFill `xml:"a:solidFill"` PrstDash *attrValString `xml:"a:prstDash"` } // cTxPr (Text Properties) directly maps the txPr element. This element // specifies text formatting. The lstStyle element is not supported. type cTxPr struct { BodyPr aBodyPr `xml:"a:bodyPr,omitempty"` LstStyle string `xml:"a:lstStyle,omitempty"` P aP `xml:"a:p,omitempty"` } // aEndParaRPr (End Paragraph Run Properties) directly maps the a:endParaRPr // element. This element specifies the text run properties that are to be used // if another run is inserted after the last run specified. This effectively // saves the run property state so that it can be applied when the user enters // additional text. If this element is omitted, then the application can // determine which default properties to apply. It is recommended that this // element be specified at the end of the list of text runs within the paragraph // so that an orderly list is maintained. type aEndParaRPr struct { Lang string `xml:"lang,attr"` AltLang string `xml:"altLang,attr,omitempty"` Sz int `xml:"sz,attr,omitempty"` } // cAutoTitleDeleted (Auto Title Is Deleted) directly maps the // autoTitleDeleted element. This element specifies the title shall not be // shown for this chart. type cAutoTitleDeleted struct { Val bool `xml:"val,attr"` } // cView3D (View In 3D) directly maps the view3D element. This element // specifies the 3-D view of the chart. type cView3D struct { RotX *attrValInt `xml:"rotX"` RotY *attrValInt `xml:"rotY"` RAngAx *attrValInt `xml:"rAngAx"` DepthPercent *attrValInt `xml:"depthPercent"` Perspective *attrValInt `xml:"perspective"` ExtLst *xlsxExtLst `xml:"extLst"` } // cPlotArea directly maps the plotArea element. This element specifies the // plot area of the chart. type cPlotArea struct { Layout *string `xml:"layout"` AreaChart []*cCharts `xml:"areaChart"` Area3DChart []*cCharts `xml:"area3DChart"` BarChart []*cCharts `xml:"barChart"` Bar3DChart []*cCharts `xml:"bar3DChart"` BubbleChart []*cCharts `xml:"bubbleChart"` DoughnutChart []*cCharts `xml:"doughnutChart"` LineChart []*cCharts `xml:"lineChart"` Line3DChart []*cCharts `xml:"line3DChart"` StockChart []*cCharts `xml:"stockChart"` PieChart []*cCharts `xml:"pieChart"` Pie3DChart []*cCharts `xml:"pie3DChart"` OfPieChart []*cCharts `xml:"ofPieChart"` RadarChart []*cCharts `xml:"radarChart"` ScatterChart []*cCharts `xml:"scatterChart"` Surface3DChart []*cCharts `xml:"surface3DChart"` SurfaceChart []*cCharts `xml:"surfaceChart"` CatAx []*cAxs `xml:"catAx"` ValAx []*cAxs `xml:"valAx"` DateAx []*cAxs `xml:"dateAx"` SerAx []*cAxs `xml:"serAx"` DTable *cDTable `xml:"dTable"` SpPr *cSpPr `xml:"spPr"` } // cCharts specifies the common element of the chart. type cCharts struct { BarDir *attrValString `xml:"barDir"` BubbleScale *attrValFloat `xml:"bubbleScale"` Grouping *attrValString `xml:"grouping"` RadarStyle *attrValString `xml:"radarStyle"` ScatterStyle *attrValString `xml:"scatterStyle"` OfPieType *attrValString `xml:"ofPieType"` VaryColors *attrValBool `xml:"varyColors"` Wireframe *attrValBool `xml:"wireframe"` Ser *[]cSer `xml:"ser"` SplitPos *attrValInt `xml:"splitPos"` SerLines *attrValString `xml:"serLines"` DLbls *cDLbls `xml:"dLbls"` DropLines *cChartLines `xml:"dropLines"` HiLowLines *cChartLines `xml:"hiLowLines"` UpDownBars *cUpDownBars `xml:"upDownBars"` GapWidth *attrValInt `xml:"gapWidth"` Shape *attrValString `xml:"shape"` HoleSize *attrValInt `xml:"holeSize"` Smooth *attrValBool `xml:"smooth"` Overlap *attrValInt `xml:"overlap"` AxID []*attrValInt `xml:"axId"` } // cAxs directly maps the catAx and valAx element. type cAxs struct { AxID *attrValInt `xml:"axId"` Scaling *cScaling `xml:"scaling"` Delete *attrValBool `xml:"delete"` AxPos *attrValString `xml:"axPos"` MajorGridlines *cChartLines `xml:"majorGridlines"` MinorGridlines *cChartLines `xml:"minorGridlines"` Title *cTitle `xml:"title"` NumFmt *cNumFmt `xml:"numFmt"` MajorTickMark *attrValString `xml:"majorTickMark"` MinorTickMark *attrValString `xml:"minorTickMark"` TickLblPos *attrValString `xml:"tickLblPos"` SpPr *cSpPr `xml:"spPr"` TxPr *cTxPr `xml:"txPr"` CrossAx *attrValInt `xml:"crossAx"` Crosses *attrValString `xml:"crosses"` CrossBetween *attrValString `xml:"crossBetween"` MajorUnit *attrValFloat `xml:"majorUnit"` MinorUnit *attrValFloat `xml:"minorUnit"` Auto *attrValBool `xml:"auto"` LblAlgn *attrValString `xml:"lblAlgn"` LblOffset *attrValInt `xml:"lblOffset"` TickLblSkip *attrValInt `xml:"tickLblSkip"` TickMarkSkip *attrValInt `xml:"tickMarkSkip"` NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"` } // cUpDownBars directly maps the upDownBars lement. This element specifies // the up and down bars. type cUpDownBars struct { GapWidth *attrValString `xml:"gapWidth"` UpBars *cChartLines `xml:"upBars"` DownBars *cChartLines `xml:"downBars"` ExtLst *xlsxExtLst `xml:"extLst"` } // cChartLines directly maps the chart lines content model. type cChartLines struct { SpPr *cSpPr `xml:"spPr"` } // cScaling directly maps the scaling element. This element contains // additional axis settings. type cScaling struct { LogBase *attrValFloat `xml:"logBase"` Orientation *attrValString `xml:"orientation"` Max *attrValFloat `xml:"max"` Min *attrValFloat `xml:"min"` } // cNumFmt (Numbering Format) directly maps the numFmt element. This element // specifies number formatting for the parent element. type cNumFmt struct { FormatCode string `xml:"formatCode,attr"` SourceLinked bool `xml:"sourceLinked,attr"` } // cSer directly maps the ser element. This element specifies a series on a // chart. type cSer struct { IDx *attrValInt `xml:"idx"` Order *attrValInt `xml:"order"` Tx *cTx `xml:"tx"` SpPr *cSpPr `xml:"spPr"` DPt []*cDPt `xml:"dPt"` DLbls *cDLbls `xml:"dLbls"` Marker *cMarker `xml:"marker"` InvertIfNegative *attrValBool `xml:"invertIfNegative"` Cat *cCat `xml:"cat"` Val *cVal `xml:"val"` XVal *cCat `xml:"xVal"` YVal *cVal `xml:"yVal"` Smooth *attrValBool `xml:"smooth"` BubbleSize *cVal `xml:"bubbleSize"` Bubble3D *attrValBool `xml:"bubble3D"` } // cMarker (Marker) directly maps the marker element. This element specifies a // data marker. type cMarker struct { Symbol *attrValString `xml:"symbol"` Size *attrValInt `xml:"size"` SpPr *cSpPr `xml:"spPr"` } // cDPt (Data Point) directly maps the dPt element. This element specifies a // single data point. type cDPt struct { IDx *attrValInt `xml:"idx"` Bubble3D *attrValBool `xml:"bubble3D"` SpPr *cSpPr `xml:"spPr"` } // cCat (Category Axis Data) directly maps the cat element. This element // specifies the data used for the category axis. type cCat struct { StrRef *cStrRef `xml:"strRef"` } // cStrRef (String Reference) directly maps the strRef element. This element // specifies a reference to data for a single data label or title with a cache // of the last values used. type cStrRef struct { F string `xml:"f"` StrCache *cStrCache `xml:"strCache"` } // cStrCache (String Cache) directly maps the strCache element. This element // specifies the last string data used for a chart. type cStrCache struct { Pt []*cPt `xml:"pt"` PtCount *attrValInt `xml:"ptCount"` } // cPt directly maps the pt element. This element specifies data for a // particular data point. type cPt struct { IDx int `xml:"idx,attr"` V *string `xml:"v"` } // cVal directly maps the val element. This element specifies the data values // which shall be used to define the location of data markers on a chart. type cVal struct { NumRef *cNumRef `xml:"numRef"` } // cNumRef directly maps the numRef element. This element specifies a // reference to numeric data with a cache of the last values used. type cNumRef struct { F string `xml:"f"` NumCache *cNumCache `xml:"numCache"` } // cNumCache directly maps the numCache element. This element specifies the // last data shown on the chart for a series. type cNumCache struct { FormatCode string `xml:"formatCode"` Pt []*cPt `xml:"pt"` PtCount *attrValInt `xml:"ptCount"` } // cDLbls (Data Labels) directly maps the dLbls element. This element serves // as a root element that specifies the settings for the data labels for an // entire series or the entire chart. It contains child elements that specify // the specific formatting and positioning settings. type cDLbls struct { NumFmt *cNumFmt `xml:"numFmt"` SpPr *cSpPr `xml:"spPr"` TxPr *cTxPr `xml:"txPr"` DLblPos *attrValString `xml:"dLblPos"` ShowLegendKey *attrValBool `xml:"showLegendKey"` ShowVal *attrValBool `xml:"showVal"` ShowCatName *attrValBool `xml:"showCatName"` ShowSerName *attrValBool `xml:"showSerName"` ShowPercent *attrValBool `xml:"showPercent"` ShowBubbleSize *attrValBool `xml:"showBubbleSize"` ShowLeaderLines *attrValBool `xml:"showLeaderLines"` } // cLegendEntry (Legend Entry) directly maps the legendEntry element. This // element specifies the legend entry. type cLegendEntry struct { IDx *attrValInt `xml:"idx"` TxPr *cTxPr `xml:"txPr"` } // cLegend (Legend) directly maps the legend element. This element specifies // the legend. type cLegend struct { Layout *string `xml:"layout"` LegendPos *attrValString `xml:"legendPos"` LegendEntry []cLegendEntry `xml:"legendEntry"` Overlay *attrValBool `xml:"overlay"` SpPr *cSpPr `xml:"spPr"` TxPr *cTxPr `xml:"txPr"` } // cPrintSettings directly maps the printSettings element. This element // specifies the print settings for the chart. type cPrintSettings struct { HeaderFooter *string `xml:"headerFooter"` PageMargins *cPageMargins `xml:"pageMargins"` PageSetup *string `xml:"pageSetup"` } // cPageMargins directly maps the pageMargins element. This element specifies // the page margins for a chart. type cPageMargins struct { B float64 `xml:"b,attr"` Footer float64 `xml:"footer,attr"` Header float64 `xml:"header,attr"` L float64 `xml:"l,attr"` R float64 `xml:"r,attr"` T float64 `xml:"t,attr"` } // ChartNumFmt directly maps the number format settings of the chart. type ChartNumFmt struct { CustomNumFmt string SourceLinked bool } // ChartAxis directly maps the format settings of the chart axis. type ChartAxis struct { None bool DropLines bool HighLowLines bool MajorGridLines bool MinorGridLines bool MajorUnit float64 TickLabelPosition ChartTickLabelPositionType TickLabelSkip int ReverseOrder bool Secondary bool Maximum *float64 Minimum *float64 Alignment Alignment Font Font LogBase float64 NumFmt ChartNumFmt Title []RichTextRun axID int } // ChartDimension directly maps the dimension of the chart. type ChartDimension struct { Width uint Height uint } // ChartUpDownBar directly maps the format settings of the stock chart up bars // and down bars. type ChartUpDownBar struct { Fill Fill Border ChartLine } // ChartPlotArea directly maps the format settings of the plot area. type ChartPlotArea struct { SecondPlotValues int ShowBubbleSize bool ShowCatName bool ShowDataTable bool ShowDataTableKeys bool ShowLeaderLines bool ShowPercent bool ShowSerName bool ShowVal bool Fill Fill UpBars ChartUpDownBar DownBars ChartUpDownBar NumFmt ChartNumFmt } // Chart directly maps the format settings of the chart. type Chart struct { Type ChartType Series []ChartSeries Format GraphicOptions Dimension ChartDimension Legend ChartLegend Title []RichTextRun VaryColors *bool XAxis ChartAxis YAxis ChartAxis PlotArea ChartPlotArea Fill Fill Border ChartLine ShowBlanksAs string BubbleSize int HoleSize int GapWidth *uint Overlap *int order int } // ChartLegend directly maps the format settings of the chart legend. type ChartLegend struct { Position string ShowLegendKey bool Font *Font } // ChartMarker directly maps the format settings of the chart marker. type ChartMarker struct { Border ChartLine Fill Fill Symbol string Size int } // ChartLine directly maps the format settings of the chart line. type ChartLine struct { Type ChartLineType Dash ChartDashType Fill Fill Smooth bool Width float64 } // ChartDataLabel directly maps the format settings of the chart labels. type ChartDataLabel struct { Alignment Alignment Font Font Fill Fill } // ChartDataPoint directly maps the format settings of the chart data point for // doughnut, pie and 3D pie charts. type ChartDataPoint struct { Index int Fill Fill } // ChartSeries directly maps the format settings of the chart series. type ChartSeries struct { Name string Categories string Values string Sizes string Fill Fill Legend ChartLegend Line ChartLine Marker ChartMarker DataLabel ChartDataLabel DataLabelPosition ChartDataLabelPositionType DataPoint []ChartDataPoint } ================================================ FILE: xmlChartSheet.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // struct code generated by github.com/xuri/xgen // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in // a SpreadsheetML document. type xlsxChartsheet struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"` SheetPr *xlsxChartsheetPr `xml:"sheetPr"` SheetViews *xlsxChartsheetViews `xml:"sheetViews"` SheetProtection *xlsxChartsheetProtection `xml:"sheetProtection"` CustomSheetViews *xlsxCustomChartsheetViews `xml:"customSheetViews"` PageMargins *xlsxPageMargins `xml:"pageMargins"` PageSetup *xlsxPageSetUp `xml:"pageSetup"` HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` Drawing *xlsxDrawing `xml:"drawing"` DrawingHF *xlsxDrawingHF `xml:"drawingHF"` Picture *xlsxPicture `xml:"picture"` WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxChartsheetPr specifies chart sheet properties. type xlsxChartsheetPr struct { XMLName xml.Name `xml:"sheetPr"` PublishedAttr bool `xml:"published,attr,omitempty"` CodeNameAttr string `xml:"codeName,attr,omitempty"` TabColor *xlsxColor `xml:"tabColor"` } // xlsxChartsheetViews specifies chart sheet views. type xlsxChartsheetViews struct { XMLName xml.Name `xml:"sheetViews"` SheetView []*xlsxChartsheetView `xml:"sheetView"` ExtLst []*xlsxExtLst `xml:"extLst"` } // xlsxChartsheetView defines custom view properties for chart sheets. type xlsxChartsheetView struct { XMLName xml.Name `xml:"sheetView"` TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"` ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"` WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"` ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"` ExtLst []*xlsxExtLst `xml:"extLst"` } // xlsxChartsheetProtection collection expresses the chart sheet protection // options to enforce when the chart sheet is protected. type xlsxChartsheetProtection struct { XMLName xml.Name `xml:"sheetProtection"` AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"` HashValueAttr []byte `xml:"hashValue,attr,omitempty"` SaltValueAttr []byte `xml:"saltValue,attr,omitempty"` SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"` ContentAttr bool `xml:"content,attr,omitempty"` ObjectsAttr bool `xml:"objects,attr,omitempty"` } // xlsxCustomChartsheetViews collection of custom Chart Sheet View // information. type xlsxCustomChartsheetViews struct { XMLName xml.Name `xml:"customSheetViews"` CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"` } // xlsxCustomChartsheetView defines custom view properties for chart sheets. type xlsxCustomChartsheetView struct { XMLName xml.Name `xml:"customSheetView"` GUIDAttr string `xml:"guid,attr"` ScaleAttr uint32 `xml:"scale,attr,omitempty"` StateAttr string `xml:"state,attr,omitempty"` ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"` PageMargins []*xlsxPageMargins `xml:"pageMargins"` PageSetup []*xlsxPageSetUp `xml:"pageSetup"` HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"` } ================================================ FILE: xmlComments.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxComments directly maps the comments element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. A comment is a // rich text note that is attached to and associated with a cell, separate from // other cell content. Comment content is stored separate from the cell, and is // displayed in a drawing object (like a text box) that is separate from, but // associated with, a cell. Comments are used as reminders, such as noting how a // complex formula works, or to provide feedback to other users. Comments can // also be used to explain assumptions made in a formula or to call out // something special about the cell. type xlsxComments struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"` Authors xlsxAuthor `xml:"authors"` CommentList xlsxCommentList `xml:"commentList"` cells []string } // xlsxAuthor directly maps the author element. This element holds a string // representing the name of a single author of comments. Every comment shall // have an author. The maximum length of the author string is an implementation // detail, but a good guideline is 255 chars. type xlsxAuthor struct { Author []string `xml:"author"` } // xlsxCommentList (List of Comments) directly maps the xlsxCommentList element. // This element is a container that holds a list of comments for the sheet. type xlsxCommentList struct { Comment []xlsxComment `xml:"comment"` } // xlsxComment directly maps the comment element. This element represents a // single user entered comment. Each comment shall have an author and can // optionally contain richly formatted text. type xlsxComment struct { Ref string `xml:"ref,attr"` AuthorID int `xml:"authorId,attr"` Text xlsxText `xml:"text"` } // xlsxText directly maps the text element. This element contains rich text // which represents the text of a comment. The maximum length for this text is a // spreadsheet application implementation detail. A recommended guideline is // 32767 chars. type xlsxText struct { T *string `xml:"t"` R []xlsxR `xml:"r"` RPh *xlsxPhoneticRun `xml:"rPh"` PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` } // xlsxPhoneticRun element represents a run of text which displays a phonetic // hint for this String Item (si). Phonetic hints are used to give information // about the pronunciation of an East Asian language. The hints are displayed // as text within the spreadsheet cells across the top portion of the cell. type xlsxPhoneticRun struct { Sb uint32 `xml:"sb,attr"` Eb uint32 `xml:"eb,attr"` T string `xml:"t"` } // Comment directly maps the comment information. type Comment struct { Author string AuthorID int Cell string Text string Width uint Height uint Paragraph []RichTextRun } ================================================ FILE: xmlContentTypes.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxTypes directly maps the types' element of content types for relationship // parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a // value. type xlsxTypes struct { mu sync.Mutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"` Defaults []xlsxDefault `xml:"Default"` Overrides []xlsxOverride `xml:"Override"` } // xlsxOverride directly maps the override element in the namespace // http://schemas.openxmlformats.org/package/2006/content-types type xlsxOverride struct { PartName string `xml:",attr"` ContentType string `xml:",attr"` } // xlsxDefault directly maps the default element in the namespace // http://schemas.openxmlformats.org/package/2006/content-types type xlsxDefault struct { Extension string `xml:",attr"` ContentType string `xml:",attr"` } ================================================ FILE: xmlCore.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // DocProperties directly maps the document core properties. type DocProperties struct { Category string ContentStatus string Created string Creator string Description string Identifier string Keywords string LastModifiedBy string Modified string Revision string Subject string Title string Language string Version string } // decodeDcTerms directly maps the DCMI metadata terms for the coreProperties. type decodeDcTerms struct { Text string `xml:",chardata"` Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` } // decodeCoreProperties directly maps the root element for a part of this // content type shall coreProperties. In order to solve the problem that the // label structure is changed after serialization and deserialization, two // different structures are defined. decodeCoreProperties just for // deserialization. type decodeCoreProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"` Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"` Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"` Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"` Keywords string `xml:"keywords,omitempty"` Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"` LastModifiedBy string `xml:"lastModifiedBy"` Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"` Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"` Revision string `xml:"revision,omitempty"` Created *decodeDcTerms `xml:"http://purl.org/dc/terms/ created"` Modified *decodeDcTerms `xml:"http://purl.org/dc/terms/ modified"` ContentStatus string `xml:"contentStatus,omitempty"` Category string `xml:"category,omitempty"` Version string `xml:"version,omitempty"` } // xlsxDcTerms directly maps the DCMI metadata terms for the coreProperties. type xlsxDcTerms struct { Text string `xml:",chardata"` Type string `xml:"xsi:type,attr"` } // xlsxCoreProperties directly maps the root element for a part of this // content type shall coreProperties. type xlsxCoreProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"` Dc string `xml:"xmlns:dc,attr"` Dcterms string `xml:"xmlns:dcterms,attr"` Dcmitype string `xml:"xmlns:dcmitype,attr"` XSI string `xml:"xmlns:xsi,attr"` Title string `xml:"dc:title,omitempty"` Subject string `xml:"dc:subject,omitempty"` Creator string `xml:"dc:creator"` Keywords string `xml:"keywords,omitempty"` Description string `xml:"dc:description,omitempty"` LastModifiedBy string `xml:"lastModifiedBy"` Language string `xml:"dc:language,omitempty"` Identifier string `xml:"dc:identifier,omitempty"` Revision string `xml:"revision,omitempty"` Created *xlsxDcTerms `xml:"dcterms:created"` Modified *xlsxDcTerms `xml:"dcterms:modified"` ContentStatus string `xml:"contentStatus,omitempty"` Category string `xml:"category,omitempty"` Version string `xml:"version,omitempty"` } ================================================ FILE: xmlCustom.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxCustomProperties directly maps the element for the custom file properties // part, that represents additional information. The information can be used as // metadata for XML. type xlsxCustomProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties Properties"` Vt string `xml:"xmlns:vt,attr"` Property []xlsxProperty `xml:"property"` } // xlsxProperty directly maps the element specifies a single custom file // property. Custom file property type is defined through child elements in the // File Properties Variant Type namespace. Custom file property value can be set // by setting the appropriate Variant Type child element value. type xlsxProperty struct { XMLName xml.Name `xml:"property"` FmtID string `xml:"fmtid,attr"` PID int `xml:"pid,attr"` Name string `xml:"name,attr,omitempty"` LinkTarget string `xml:"linkTarget,attr,omitempty"` Vector *string `xml:"vt:vector"` Array *string `xml:"vt:array"` Blob *string `xml:"vt:blob"` Oblob *string `xml:"vt:oblob"` Empty *string `xml:"vt:empty"` Null *string `xml:"vt:null"` I1 *int8 `xml:"vt:i1"` I2 *int16 `xml:"vt:i2"` I4 *int32 `xml:"vt:i4"` I8 *int64 `xml:"vt:i8"` Int *int `xml:"vt:int"` Ui1 *uint8 `xml:"vt:ui1"` Ui2 *uint16 `xml:"vt:ui2"` Ui4 *uint32 `xml:"vt:ui4"` Ui8 *uint64 `xml:"vt:ui8"` Uint *uint `xml:"vt:uint"` R4 *float32 `xml:"vt:r4"` R8 *float64 `xml:"vt:r8"` Decimal *string `xml:"vt:decimal"` Lpstr *string `xml:"vt:lpstr"` Lpwstr *string `xml:"vt:lpwstr"` Bstr *string `xml:"vt:bstr"` Date *string `xml:"vt:date"` FileTime *string `xml:"vt:filetime"` Bool *bool `xml:"vt:bool"` Cy *string `xml:"vt:cy"` Error *string `xml:"vt:error"` Stream *string `xml:"vt:stream"` Ostream *string `xml:"vt:ostream"` Storage *string `xml:"vt:storage"` Ostorage *string `xml:"vt:ostorage"` Vstream *string `xml:"vt:vstream"` ClsID *string `xml:"vt:clsid"` } // decodeCustomProperties specifies to an OOXML document custom properties. // decodeCustomProperties just for deserialization. type decodeCustomProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties Properties"` Vt string `xml:"xmlns:vt,attr"` Property []decodeProperty `xml:"property"` } // decodeProperty specifies to an OOXML document custom property. decodeProperty // just for deserialization. type decodeProperty struct { XMLName xml.Name `xml:"property"` FmtID string `xml:"fmtid,attr"` PID int `xml:"pid,attr"` Name string `xml:"name,attr,omitempty"` LinkTarget string `xml:"linkTarget,attr,omitempty"` Vector *string `xml:"vector"` Array *string `xml:"array"` Blob *string `xml:"blob"` Oblob *string `xml:"oblob"` Empty *string `xml:"empty"` Null *string `xml:"null"` I1 *int8 `xml:"i1"` I2 *int16 `xml:"i2"` I4 *int32 `xml:"i4"` I8 *int64 `xml:"i8"` Int *int `xml:"int"` Ui1 *uint8 `xml:"ui1"` Ui2 *uint16 `xml:"ui2"` Ui4 *uint32 `xml:"ui4"` Ui8 *uint64 `xml:"ui8"` Uint *uint `xml:"uint"` R4 *float32 `xml:"r4"` R8 *float64 `xml:"r8"` Decimal *string `xml:"decimal"` Lpstr *string `xml:"lpstr"` Lpwstr *string `xml:"lpwstr"` Bstr *string `xml:"bstr"` Date *string `xml:"date"` FileTime *string `xml:"filetime"` Bool *bool `xml:"bool"` Cy *string `xml:"cy"` Error *string `xml:"error"` Stream *string `xml:"stream"` Ostream *string `xml:"ostream"` Storage *string `xml:"storage"` Ostorage *string `xml:"ostorage"` Vstream *string `xml:"vstream"` ClsID *string `xml:"clsid"` } // CustomProperty directly maps the custom property of the workbook. The value // date type may be one of the following: int32, float64, string, bool, // time.Time, or nil. type CustomProperty struct { Name string Value interface{} } ================================================ FILE: xmlDecodeDrawing.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape // Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element // specifies a two cell anchor placeholder for a group, a shape, or a drawing // element. It moves with cells and its extents are in EMU units. type decodeCellAnchor struct { EditAs string `xml:"editAs,attr,omitempty"` From *decodeFrom `xml:"from"` To *decodeTo `xml:"to"` Ext *decodePositiveSize2D `xml:"ext"` Sp *decodeSp `xml:"sp"` Pic *decodePic `xml:"pic"` ClientData *decodeClientData `xml:"clientData"` AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"` Content string `xml:",innerxml"` } // decodeCellAnchorPos defines the structure used to deserialize the cell anchor // for adjust drawing object on inserting/deleting column/rows. type decodeCellAnchorPos struct { EditAs string `xml:"editAs,attr,omitempty"` From *xlsxFrom `xml:"from"` To *xlsxTo `xml:"to"` Pos *xlsxInnerXML `xml:"pos"` Ext *xlsxPositiveSize2D `xml:"ext"` Sp *xlsxSp `xml:"sp"` GrpSp *xlsxInnerXML `xml:"grpSp"` GraphicFrame *xlsxInnerXML `xml:"graphicFrame"` CxnSp *xlsxInnerXML `xml:"cxnSp"` Pic *xlsxInnerXML `xml:"pic"` ContentPart *xlsxInnerXML `xml:"contentPart"` AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"` ClientData *xlsxInnerXML `xml:"clientData"` } // decodeChoice defines the structure used to deserialize the mc:Choice element. type decodeChoice struct { XMLName xml.Name `xml:"Choice"` XMLNSA14 string `xml:"a14,attr"` XMLNSSle15 string `xml:"sle15,attr"` Requires string `xml:"Requires,attr"` GraphicFrame decodeGraphicFrame `xml:"graphicFrame"` } // decodeGraphicFrame defines the structure used to deserialize the // xdr:graphicFrame element. type decodeGraphicFrame struct { Macro string `xml:"macro,attr"` NvGraphicFramePr decodeNvGraphicFramePr `xml:"nvGraphicFramePr"` } // decodeNvGraphicFramePr defines the structure used to deserialize the // xdr:nvGraphicFramePr element. type decodeNvGraphicFramePr struct { CNvPr decodeCNvPr `xml:"cNvPr"` } // decodeSp defines the structure used to deserialize the sp element. type decodeSp struct { Macro string `xml:"macro,attr,omitempty"` TextLink string `xml:"textlink,attr,omitempty"` FLocksText bool `xml:"fLocksText,attr,omitempty"` FPublished *bool `xml:"fPublished,attr"` NvSpPr *decodeNvSpPr `xml:"nvSpPr"` SpPr *decodeSpPr `xml:"spPr"` } // decodeNvSpPr (Non-Visual Properties for a Shape) directly maps the nvSpPr // element. This element specifies all non-visual properties for a shape. This // element is a container for the non-visual identification properties, shape // properties and application properties that are to be associated with a // shape. This allows for additional information that does not affect the // appearance of the shape to be stored. type decodeNvSpPr struct { CNvPr *decodeCNvPr `xml:"cNvPr"` ExtLst *decodePositiveSize2D `xml:"extLst"` CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"` } // decodeCNvSpPr (Connection Non-Visual Shape Properties) directly maps the // cNvSpPr element. This element specifies the set of non-visual properties // for a connection shape. These properties specify all data about the // connection shape which do not affect its display within a spreadsheet. type decodeCNvSpPr struct { TxBox bool `xml:"txBox,attr"` } // decodeWsDr directly maps the root element for a part of this content type // shall wsDr. In order to solve the problem that the label structure is // changed after serialization and deserialization, two different structures // are defined. decodeWsDr just for deserialization. type decodeWsDr struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr"` A string `xml:"xmlns a,attr"` Xdr string `xml:"xmlns xdr,attr"` R string `xml:"xmlns r,attr"` AlternateContent []*xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"` TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"` } // decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This // element specifies non-visual canvas properties. This allows for additional // information that does not affect the appearance of the picture to be // stored. type decodeCNvPr struct { XMLName xml.Name `xml:"cNvPr"` ID int `xml:"id,attr"` Name string `xml:"name,attr"` Descr string `xml:"descr,attr"` Title string `xml:"title,attr,omitempty"` HlinkClick *decodeHlinkClick `xml:"hlinkClick,omitempty"` } // decodeHlinkClick directly maps the hlinkClick (Hyperlink Click).This element // specifies the on-click hyperlink information to be applied to a run of text. // When the hyperlink text is clicked the link is fetched. // clicked the link is fetched. type decodeHlinkClick struct { RID string `xml:"id,attr,omitempty"` InvalidURL string `xml:"invalidUrl,attr,omitempty"` Action string `xml:"action,attr,omitempty"` TgtFrame string `xml:"tgtFrame,attr,omitempty"` Tooltip string `xml:"tooltip,attr,omitempty"` History bool `xml:"history,attr,omitempty"` HighlightClick bool `xml:"highlightClick,attr,omitempty"` EndSnd bool `xml:"endSnd,attr,omitempty"` } // decodePicLocks directly maps the picLocks (Picture Locks). This element // specifies all locking properties for a graphic frame. These properties // inform the generating application about specific properties that have been // previously locked and thus should not be changed. type decodePicLocks struct { NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"` NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"` NoChangeAspect bool `xml:"noChangeAspect,attr"` NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"` NoCrop bool `xml:"noCrop,attr,omitempty"` NoEditPoints bool `xml:"noEditPoints,attr,omitempty"` NoGrp bool `xml:"noGrp,attr,omitempty"` NoMove bool `xml:"noMove,attr,omitempty"` NoResize bool `xml:"noResize,attr,omitempty"` NoRot bool `xml:"noRot,attr,omitempty"` NoSelect bool `xml:"noSelect,attr,omitempty"` } // decodeBlip element specifies the existence of an image (binary large image // or picture) and contains a reference to the image data. type decodeBlip struct { Embed string `xml:"embed,attr"` Cstate string `xml:"cstate,attr,omitempty"` R string `xml:"r,attr"` } // decodeStretch directly maps the stretch element. This element specifies // that a BLIP should be stretched to fill the target rectangle. The other // option is a tile where a BLIP is tiled to fill the available area. type decodeStretch struct { FillRect string `xml:"fillRect"` } // decodeOff directly maps the colOff and rowOff element. This element is used // to specify the column offset within a cell. type decodeOff struct { X int `xml:"x,attr"` Y int `xml:"y,attr"` } // decodePositiveSize2D directly maps the a:ext element. type decodePositiveSize2D struct { Cx int `xml:"cx,attr"` Cy int `xml:"cy,attr"` } // decodePrstGeom directly maps the prstGeom (Preset geometry). This element // specifies when a preset geometric shape should be used instead of a custom // geometric shape. The generating application should be able to render all // preset geometries enumerated in the ST_ShapeType list. type decodePrstGeom struct { Prst string `xml:"prst,attr"` } // decodeXfrm directly maps the xfrm (2D Transform for Graphic Frame). This // element specifies the transform to be applied to the corresponding graphic // frame. This transformation is applied to the graphic frame just as it would // be for a shape or group shape. type decodeXfrm struct { Off decodeOff `xml:"off"` Ext decodePositiveSize2D `xml:"ext"` } // decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing // Properties). This element specifies the non-visual properties for the picture // canvas. These properties are to be used by the generating application to // determine how certain properties are to be changed for the picture object in // question. type decodeCNvPicPr struct { PicLocks decodePicLocks `xml:"picLocks"` } // directly maps the nvPicPr (Non-Visual Properties for a Picture). This // element specifies all non-visual properties for a picture. This element is // a container for the non-visual identification properties, shape properties // and application properties that are to be associated with a picture. This // allows for additional information that does not affect the appearance of // the picture to be stored. type decodeNvPicPr struct { CNvPr decodeCNvPr `xml:"cNvPr"` CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"` } // decodeBlipFill directly maps the blipFill (Picture Fill). This element // specifies the kind of picture fill that the picture object has. Because a // picture has a picture fill already by default, it is possible to have two // fills specified for a picture object. type decodeBlipFill struct { Blip decodeBlip `xml:"blip"` Stretch decodeStretch `xml:"stretch"` } // decodeSpPr directly maps the spPr (Shape Properties). This element // specifies the visual shape properties that can be applied to a picture. // These are the same properties that are allowed to describe the visual // properties of a shape but are used here to describe the visual appearance // of a picture within a document. type decodeSpPr struct { Xfrm decodeXfrm `xml:"xfrm"` PrstGeom decodePrstGeom `xml:"prstGeom"` } // decodePic elements encompass the definition of pictures within the // DrawingML framework. While pictures are in many ways very similar to shapes // they have specific properties that are unique in order to optimize for // picture-specific scenarios. type decodePic struct { NvPicPr decodeNvPicPr `xml:"nvPicPr"` BlipFill decodeBlipFill `xml:"blipFill"` SpPr decodeSpPr `xml:"spPr"` } // decodeFrom specifies the starting anchor. type decodeFrom struct { Col int `xml:"col"` ColOff int `xml:"colOff"` Row int `xml:"row"` RowOff int `xml:"rowOff"` } // decodeTo directly specifies the ending anchor. type decodeTo struct { Col int `xml:"col"` ColOff int `xml:"colOff"` Row int `xml:"row"` RowOff int `xml:"rowOff"` } // decodeClientData directly maps the clientData element. An empty element // which specifies (via attributes) certain properties related to printing and // selection of the drawing object. The fLocksWithSheet attribute (either true // or false) determines whether to disable selection when the sheet is // protected, and fPrintsWithSheet attribute (either true or false) determines // whether the object is printed when the sheet is printed. type decodeClientData struct { FLocksWithSheet bool `xml:"fLocksWithSheet,attr"` FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"` } // decodeCellImages directly maps the Kingsoft WPS Office embedded cell images. type decodeCellImages struct { XMLName xml.Name `xml:"http://www.wps.cn/officeDocument/2017/etCustomData cellImages"` CellImage []decodeCellImage `xml:"cellImage"` } // decodeCellImage defines the structure used to deserialize the Kingsoft WPS // Office embedded cell images. type decodeCellImage struct { Pic decodePic `xml:"pic"` } ================================================ FILE: xmlDrawing.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This // element specifies non-visual canvas properties. This allows for additional // information that does not affect the appearance of the picture to be stored. type xlsxCNvPr struct { ID int `xml:"id,attr"` Name string `xml:"name,attr"` Descr string `xml:"descr,attr"` Title string `xml:"title,attr,omitempty"` HlinkClick *xlsxHlinkClick `xml:"a:hlinkClick"` } // xlsxHlinkClick (Click Hyperlink) Specifies the on-click hyperlink // information to be applied to a run of text. When the hyperlink text is // clicked the link is fetched. type xlsxHlinkClick struct { R string `xml:"xmlns:r,attr,omitempty"` RID string `xml:"r:id,attr,omitempty"` InvalidURL string `xml:"invalidUrl,attr,omitempty"` Action string `xml:"action,attr,omitempty"` TgtFrame string `xml:"tgtFrame,attr,omitempty"` Tooltip string `xml:"tooltip,attr,omitempty"` History bool `xml:"history,attr,omitempty"` HighlightClick bool `xml:"highlightClick,attr,omitempty"` EndSnd bool `xml:"endSnd,attr,omitempty"` } // xlsxPicLocks directly maps the picLocks (Picture Locks). This element // specifies all locking properties for a graphic frame. These properties inform // the generating application about specific properties that have been // previously locked and thus should not be changed. type xlsxPicLocks struct { NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"` NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"` NoChangeAspect bool `xml:"noChangeAspect,attr"` NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"` NoCrop bool `xml:"noCrop,attr,omitempty"` NoEditPoints bool `xml:"noEditPoints,attr,omitempty"` NoGrp bool `xml:"noGrp,attr,omitempty"` NoMove bool `xml:"noMove,attr,omitempty"` NoResize bool `xml:"noResize,attr,omitempty"` NoRot bool `xml:"noRot,attr,omitempty"` NoSelect bool `xml:"noSelect,attr,omitempty"` } // xlsxBlip element specifies the existence of an image (binary large image or // picture) and contains a reference to the image data. type xlsxBlip struct { Embed string `xml:"r:embed,attr"` Cstate string `xml:"cstate,attr,omitempty"` R string `xml:"xmlns:r,attr"` ExtList *xlsxEGOfficeArtExtensionList `xml:"a:extLst"` } // xlsxStretch directly maps the stretch element. This element specifies that a // BLIP should be stretched to fill the target rectangle. The other option is a // tile where a BLIP is tiled to fill the available area. type xlsxStretch struct { FillRect string `xml:"a:fillRect"` } // xlsxOff directly maps the colOff and rowOff element. This element is used to // specify the column offset within a cell. type xlsxOff struct { X int `xml:"x,attr"` Y int `xml:"y,attr"` } // xlsxPositiveSize2D directly maps the a:ext element. type xlsxPositiveSize2D struct { Cx int `xml:"cx,attr"` Cy int `xml:"cy,attr"` } // xlsxPrstGeom directly maps the prstGeom (Preset geometry). This element // specifies when a preset geometric shape should be used instead of a custom // geometric shape. The generating application should be able to render all // preset geometries enumerated in the ST_ShapeType list. type xlsxPrstGeom struct { Prst string `xml:"prst,attr"` } // xlsxXfrm directly maps the xfrm (2D Transform for Graphic Frame). This // element specifies the transform to be applied to the corresponding graphic // frame. This transformation is applied to the graphic frame just as it would // be for a shape or group shape. type xlsxXfrm struct { Off xlsxOff `xml:"a:off"` Ext xlsxPositiveSize2D `xml:"a:ext"` } // xlsxCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing // Properties). This element specifies the non-visual properties for the picture // canvas. These properties are to be used by the generating application to // determine how certain properties are to be changed for the picture object in // question. type xlsxCNvPicPr struct { PicLocks xlsxPicLocks `xml:"a:picLocks"` } // directly maps the nvPicPr (Non-Visual Properties for a Picture). This element // specifies all non-visual properties for a picture. This element is a // container for the non-visual identification properties, shape properties and // application properties that are to be associated with a picture. This allows // for additional information that does not affect the appearance of the picture // to be stored. type xlsxNvPicPr struct { CNvPr xlsxCNvPr `xml:"xdr:cNvPr"` CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"` } // xlsxCTSVGBlip specifies a graphic element in Scalable Vector Graphics (SVG) // format. type xlsxCTSVGBlip struct { XMLNSaAVG string `xml:"xmlns:asvg,attr"` Embed string `xml:"r:embed,attr"` Link string `xml:"r:link,attr,omitempty"` } // xlsxCTOfficeArtExtension used for future extensibility and is seen elsewhere // throughout the drawing area. type xlsxCTOfficeArtExtension struct { XMLName xml.Name `xml:"a:ext"` URI string `xml:"uri,attr"` SVGBlip xlsxCTSVGBlip `xml:"asvg:svgBlip"` } // xlsxEGOfficeArtExtensionList used for future extensibility and is seen // elsewhere throughout the drawing area. type xlsxEGOfficeArtExtensionList struct { Ext []xlsxCTOfficeArtExtension `xml:"ext"` } // xlsxBlipFill directly maps the blipFill (Picture Fill). This element // specifies the kind of picture fill that the picture object has. Because a // picture has a picture fill already by default, it is possible to have two // fills specified for a picture object. type xlsxBlipFill struct { Blip xlsxBlip `xml:"a:blip"` Stretch xlsxStretch `xml:"a:stretch"` } // xlsxLineProperties specifies the width of a line in EMUs. This simple type // has a minimum value of greater than or equal to 0. This simple type has a // maximum value of less than or equal to 20116800. type xlsxLineProperties struct { W int `xml:"w,attr,omitempty"` SolidFill *xlsxInnerXML `xml:"a:solidFill"` } // xlsxSpPr directly maps the spPr (Shape Properties). This element specifies // the visual shape properties that can be applied to a picture. These are the // same properties that are allowed to describe the visual properties of a shape // but are used here to describe the visual appearance of a picture within a // document. type xlsxSpPr struct { Xfrm xlsxXfrm `xml:"a:xfrm"` PrstGeom xlsxPrstGeom `xml:"a:prstGeom"` SolidFill *aSolidFill `xml:"a:solidFill"` Ln xlsxLineProperties `xml:"a:ln"` } // xlsxPic elements encompass the definition of pictures within the DrawingML // framework. While pictures are in many ways very similar to shapes they have // specific properties that are unique in order to optimize for picture- // specific scenarios. type xlsxPic struct { NvPicPr xlsxNvPicPr `xml:"xdr:nvPicPr"` BlipFill xlsxBlipFill `xml:"xdr:blipFill"` SpPr xlsxSpPr `xml:"xdr:spPr"` } // xlsxFrom specifies the starting anchor. type xlsxFrom struct { Col int `xml:"xdr:col"` ColOff int `xml:"xdr:colOff"` Row int `xml:"xdr:row"` RowOff int `xml:"xdr:rowOff"` } // xlsxTo directly specifies the ending anchor. type xlsxTo struct { Col int `xml:"xdr:col"` ColOff int `xml:"xdr:colOff"` Row int `xml:"xdr:row"` RowOff int `xml:"xdr:rowOff"` } // xdrClientData directly maps the clientData element. An empty element which // specifies (via attributes) certain properties related to printing and // selection of the drawing object. The fLocksWithSheet attribute (either true // or false) determines whether to disable selection when the sheet is // protected, and fPrintsWithSheet attribute (either true or false) determines // whether the object is printed when the sheet is printed. type xdrClientData struct { FLocksWithSheet bool `xml:"fLocksWithSheet,attr"` FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"` } // xdrCellAnchor specifies a oneCellAnchor (One Cell Anchor Shape Size) and // twoCellAnchor (Two Cell Anchor Shape Size) placeholder for a group, a shape, // or a drawing element. It moves with cells and its extents are in EMU units. type xdrCellAnchor struct { EditAs string `xml:"editAs,attr,omitempty"` Pos *xlsxPoint2D `xml:"xdr:pos"` From *xlsxFrom `xml:"xdr:from"` To *xlsxTo `xml:"xdr:to"` Ext *xlsxPositiveSize2D `xml:"xdr:ext"` Sp *xdrSp `xml:"xdr:sp"` Pic *xlsxPic `xml:"xdr:pic,omitempty"` GraphicFrame string `xml:",innerxml"` AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` ClientData *xdrClientData `xml:"xdr:clientData"` } // xlsxCellAnchorPos defines the structure used to serialize the cell anchor for // adjust drawing object on inserting/deleting column/rows. type xlsxCellAnchorPos struct { EditAs string `xml:"editAs,attr,omitempty"` From *xlsxFrom `xml:"xdr:from"` To *xlsxTo `xml:"xdr:to"` Pos *xlsxInnerXML `xml:"xdr:pos"` Ext *xlsxPositiveSize2D `xml:"xdr:ext"` Sp *xlsxSp `xml:"xdr:sp"` GrpSp *xlsxInnerXML `xml:"xdr:grpSp"` GraphicFrame *xlsxInnerXML `xml:"xdr:graphicFrame"` CxnSp *xlsxInnerXML `xml:"xdr:cxnSp"` Pic *xlsxInnerXML `xml:"xdr:pic"` ContentPart *xlsxInnerXML `xml:"xdr:contentPart"` AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` ClientData *xlsxInnerXML `xml:"xdr:clientData"` } // xdrSp (Shape) directly maps the sp element. This element specifies the // existence of a single shape. A shape can either be a preset or a custom // geometry, defined using the SpreadsheetDrawingML framework. In addition to // a geometry each shape can have both visual and non-visual properties // attached. Text and corresponding styling information can also be attached // to a shape. This shape is specified along with all other shapes within // either the shape tree or group shape elements. type xlsxSp struct { Macro string `xml:"macro,attr,omitempty"` TextLink string `xml:"textlink,attr,omitempty"` FLocksText bool `xml:"fLocksText,attr,omitempty"` FPublished *bool `xml:"fPublished,attr"` Content string `xml:",innerxml"` } // xlsxPoint2D describes the position of a drawing element within a spreadsheet. type xlsxPoint2D struct { XMLName xml.Name `xml:"xdr:pos"` X int `xml:"x,attr"` Y int `xml:"y,attr"` } // xlsxWsDr directly maps the root element for a part of this content type shall // wsDr. type xlsxWsDr struct { mu sync.Mutex XMLName xml.Name `xml:"xdr:wsDr"` NS string `xml:"xmlns,attr,omitempty"` A string `xml:"xmlns:a,attr,omitempty"` Xdr string `xml:"xmlns:xdr,attr,omitempty"` R string `xml:"xmlns:r,attr,omitempty"` AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"` OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"` TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"` } // xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element. // This element specifies the existence of a graphics frame. This frame contains // a graphic that was generated by an external source and needs a container in // which to be displayed on the slide surface. type xlsxGraphicFrame struct { XMLName xml.Name `xml:"xdr:graphicFrame"` Macro string `xml:"macro,attr"` NvGraphicFramePr xlsxNvGraphicFramePr `xml:"xdr:nvGraphicFramePr"` Xfrm xlsxXfrm `xml:"xdr:xfrm"` Graphic *xlsxGraphic `xml:"a:graphic"` } // xlsxNvGraphicFramePr (Non-Visual Properties for a Graphic Frame) directly // maps the xdr:nvGraphicFramePr element. This element specifies all non-visual // properties for a graphic frame. This element is a container for the non- // visual identification properties, shape properties and application properties // that are to be associated with a graphic frame. This allows for additional // information that does not affect the appearance of the graphic frame to be // stored. type xlsxNvGraphicFramePr struct { CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"` ChicNvGraphicFramePr string `xml:"xdr:cNvGraphicFramePr"` } // xlsxGraphic (Graphic Object) directly maps the a:graphic element. This // element specifies the existence of a single graphic object. Document authors // should refer to this element when they wish to persist a graphical object of // some kind. The specification for this graphical object is provided entirely // by the document author and referenced within the graphicData child element. type xlsxGraphic struct { GraphicData *xlsxGraphicData `xml:"a:graphicData"` } // xlsxGraphicData (Graphic Object Data) directly maps the a:graphicData // element. This element specifies the reference to a graphic object within the // document. This graphic object is provided entirely by the document authors // who choose to persist this data within the document. type xlsxGraphicData struct { URI string `xml:"uri,attr"` Chart *xlsxChart `xml:"c:chart,omitempty"` Sle *xlsxSle `xml:"sle:slicer"` } type xlsxSle struct { XMLNS string `xml:"xmlns:sle,attr"` Name string `xml:"name,attr"` } // xlsxChart (Chart) directly maps the c:chart element. type xlsxChart struct { C string `xml:"xmlns:c,attr"` RID string `xml:"r:id,attr"` R string `xml:"xmlns:r,attr"` } // xdrSp (Shape) directly maps the xdr:sp element. This element specifies the // existence of a single shape. A shape can either be a preset or a custom // geometry, defined using the SpreadsheetDrawingML framework. In addition to a // geometry each shape can have both visual and non-visual properties attached. // Text and corresponding styling information can also be attached to a shape. // This shape is specified along with all other shapes within either the shape // tree or group shape elements. type xdrSp struct { XMLName xml.Name `xml:"xdr:sp"` Macro string `xml:"macro,attr"` Textlink string `xml:"textlink,attr"` NvSpPr *xdrNvSpPr `xml:"xdr:nvSpPr"` SpPr *xlsxSpPr `xml:"xdr:spPr"` Style *xdrStyle `xml:"xdr:style"` TxBody *xdrTxBody `xml:"xdr:txBody"` } // xdrNvSpPr (Non-Visual Properties for a Shape) directly maps the xdr:nvSpPr // element. This element specifies all non-visual properties for a shape. This // element is a container for the non-visual identification properties, shape // properties and application properties that are to be associated with a shape. // This allows for additional information that does not affect the appearance of // the shape to be stored. type xdrNvSpPr struct { CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"` CNvSpPr *xdrCNvSpPr `xml:"xdr:cNvSpPr"` } // xdrCNvSpPr (Connection Non-Visual Shape Properties) directly maps the // xdr:cNvSpPr element. This element specifies the set of non-visual properties // for a connection shape. These properties specify all data about the // connection shape which do not affect its display within a spreadsheet. type xdrCNvSpPr struct { TxBox bool `xml:"txBox,attr"` } // xdrStyle (Shape Style) directly maps the xdr:style element. The element // specifies the style that is applied to a shape and the corresponding // references for each of the style components such as lines and fills. type xdrStyle struct { LnRef *aRef `xml:"a:lnRef"` FillRef *aRef `xml:"a:fillRef"` EffectRef *aRef `xml:"a:effectRef"` FontRef *aFontRef `xml:"a:fontRef"` } // aRef directly maps the a:lnRef, a:fillRef and a:effectRef element. type aRef struct { Idx int `xml:"idx,attr"` ScrgbClr *aScrgbClr `xml:"a:scrgbClr"` SchemeClr *attrValString `xml:"a:schemeClr"` SrgbClr *attrValString `xml:"a:srgbClr"` } // aScrgbClr (RGB Color Model - Percentage Variant) directly maps the a:scrgbClr // element. This element specifies a color using the red, green, blue RGB color // model. Each component, red, green, and blue is expressed as a percentage from // 0% to 100%. A linear gamma of 1.0 is assumed. type aScrgbClr struct { R float64 `xml:"r,attr"` G float64 `xml:"g,attr"` B float64 `xml:"b,attr"` } // aFontRef (Font Reference) directly maps the a:fontRef element. This element // represents a reference to a themed font. When used it specifies which themed // font to use along with a choice of color. type aFontRef struct { Idx string `xml:"idx,attr"` SchemeClr *attrValString `xml:"a:schemeClr"` } // xdrTxBody (Shape Text Body) directly maps the xdr:txBody element. This // element specifies the existence of text to be contained within the // corresponding shape. All visible text and visible text related properties are // contained within this element. There can be multiple paragraphs and within // paragraphs multiple runs of text. type xdrTxBody struct { BodyPr *aBodyPr `xml:"a:bodyPr"` P []*aP `xml:"a:p"` } // Picture maps the format settings of the picture. type Picture struct { Extension string File []byte Format *GraphicOptions InsertType PictureInsertType } // GraphicOptions directly maps the format settings of the picture. type GraphicOptions struct { AltText string Name string PrintObject *bool Locked *bool LockAspectRatio bool AutoFit bool AutoFitIgnoreAspect bool OffsetX int OffsetY int ScaleX float64 ScaleY float64 Hyperlink string HyperlinkType string Positioning string } // Shape directly maps the format settings of the shape. type Shape struct { Cell string Type string Macro string Width uint Height uint Format GraphicOptions Fill Fill Line ShapeLine Paragraph []RichTextRun } // ShapeLine directly maps the line settings of the shape. type ShapeLine struct { Color string Width *float64 } ================================================ FILE: xmlMetaData.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxMetadata directly maps the metadata element. A cell in a spreadsheet // application can have metadata associated with it. Metadata is just a set of // additional properties about the particular cell, and this metadata is stored // in the metadata xml part. There are two types of metadata: cell metadata and // value metadata. Cell metadata contains information about the cell itself, // and this metadata can be carried along with the cell as it moves // (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is // information about the value of a particular cell. Value metadata properties // can be propagated along with the value as it is referenced in formulas. type xlsxMetadata struct { XMLName xml.Name `xml:"metadata"` MetadataTypes *xlsxInnerXML `xml:"metadataTypes"` MetadataStrings *xlsxInnerXML `xml:"metadataStrings"` MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"` FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"` CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"` ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxFutureMetadata directly maps the futureMetadata element. This element // represents future metadata information. type xlsxFutureMetadata struct { Bk []xlsxFutureMetadataBlock `xml:"bk"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxFutureMetadataBlock directly maps the kb element. This element represents // a block of future metadata information. This is a location for storing // feature extension information. type xlsxFutureMetadataBlock struct { ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxMetadataBlocks directly maps the metadata element. This element // represents cell metadata information. Cell metadata is information metadata // about a specific cell, and it stays tied to that cell position. type xlsxMetadataBlocks struct { Count int `xml:"count,attr,omitempty"` Bk []xlsxMetadataBlock `xml:"bk"` } // xlsxMetadataBlock directly maps the bk element. This element represents a // block of metadata records. type xlsxMetadataBlock struct { Rc []xlsxMetadataRecord `xml:"rc"` } // xlsxMetadataRecord directly maps the rc element. This element represents a // reference to a specific metadata record. type xlsxMetadataRecord struct { T int `xml:"t,attr"` V int `xml:"v,attr"` } // xlsxRichValueData directly maps the rvData element that specifies rich value // data. type xlsxRichValueData struct { XMLName xml.Name `xml:"rvData"` Count int `xml:"count,attr,omitempty"` Rv []xlsxRichValue `xml:"rv"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxRichValue directly maps the rv element that specifies rich value data // information for a single rich value type xlsxRichValue struct { S int `xml:"s,attr"` V []string `xml:"v"` Fb *xlsxInnerXML `xml:"fb"` } // xlsxRichValueRels directly maps the richValueRels element. This element that // specifies a list of rich value relationships. type xlsxRichValueRels struct { XMLName xml.Name `xml:"richValueRels"` Rels []xlsxRichValueRelRelationship `xml:"rel"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxRichValueRelRelationship directly maps the rel element. This element // specifies a relationship for a rich value property. type xlsxRichValueRelRelationship struct { ID string `xml:"id,attr"` } // xlsxRichValueStructures directly maps the rvStructures element. This element // specifies rich value structures, which contain lists of rich value keys and // the data types for the corresponding rich value data. type xlsxRichValueStructures struct { XMLName xml.Name `xml:"rvStructures"` Count int `xml:"count,attr,omitempty"` S []xlsxRichValueStructure `xml:"s"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxRichValueStructure directly maps the s element. This element specifies // the list of rich value structures. type xlsxRichValueStructure struct { T string `xml:"t,attr"` K []xlsxRichValueKey `xml:"k"` } // xlsxRichValueKey directly maps the k element. This element specifies the rich // value key. type xlsxRichValueKey struct { N string `xml:"n,attr"` T string `xml:"t,attr,omitempty"` } // xlsxWebImagesSupportingRichData directly maps the webImagesSrd element. This // element specifies a list of sets of properties associated with web image rich // values. type xlsxWebImagesSupportingRichData struct { XMLName xml.Name `xml:"webImagesSrd"` WebImageSrd []xlsxWebImageSupportingRichData `xml:"webImageSrd"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxWebImageSupportingRichData directly maps the webImageSrd element. This // element specifies a set of properties for a web image rich value. type xlsxWebImageSupportingRichData struct { Address xlsxExternalReference `xml:"address"` MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"` Blip xlsxExternalReference `xml:"blip"` } ================================================ FILE: xmlPivotCache.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part // defines each field in the source data, including the name, the string // resources of the instance data (for shared items), and information about // the type of data that appears in the field. type xlsxPivotCacheDefinition struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` Invalid bool `xml:"invalid,attr,omitempty"` SaveData bool `xml:"saveData,attr"` RefreshOnLoad bool `xml:"refreshOnLoad,attr,omitempty"` OptimizeMemory bool `xml:"optimizeMemory,attr,omitempty"` EnableRefresh bool `xml:"enableRefresh,attr,omitempty"` RefreshedBy string `xml:"refreshedBy,attr,omitempty"` RefreshedDate float64 `xml:"refreshedDate,attr,omitempty"` RefreshedDateIso float64 `xml:"refreshedDateIso,attr,omitempty"` BackgroundQuery bool `xml:"backgroundQuery,attr"` MissingItemsLimit int `xml:"missingItemsLimit,attr,omitempty"` CreatedVersion int `xml:"createdVersion,attr,omitempty"` RefreshedVersion int `xml:"refreshedVersion,attr,omitempty"` MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"` RecordCount int `xml:"recordCount,attr,omitempty"` UpgradeOnRefresh bool `xml:"upgradeOnRefresh,attr,omitempty"` TupleCacheAttr bool `xml:"tupleCache,attr,omitempty"` SupportSubquery bool `xml:"supportSubquery,attr,omitempty"` SupportAdvancedDrill bool `xml:"supportAdvancedDrill,attr,omitempty"` CacheSource *xlsxCacheSource `xml:"cacheSource"` CacheFields *xlsxCacheFields `xml:"cacheFields"` CacheHierarchies *xlsxCacheHierarchies `xml:"cacheHierarchies"` Kpis *xlsxKpis `xml:"kpis"` TupleCache *xlsxTupleCache `xml:"tupleCache"` CalculatedItems *xlsxCalculatedItems `xml:"calculatedItems"` CalculatedMembers *xlsxCalculatedMembers `xml:"calculatedMembers"` Dimensions *xlsxDimensions `xml:"dimensions"` MeasureGroups *xlsxMeasureGroups `xml:"measureGroups"` Maps *xlsxMaps `xml:"maps"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxCacheSource represents the description of data source whose data is // stored in the pivot cache. The data source refers to the underlying rows or // database records that provide the data for a PivotTable. You can create a // PivotTable report from a SpreadsheetML table, an external database // (including OLAP cubes), multiple SpreadsheetML worksheets, or another // PivotTable. type xlsxCacheSource struct { Type string `xml:"type,attr"` ConnectionID int `xml:"connectionId,attr,omitempty"` WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"` Consolidation *xlsxConsolidation `xml:"consolidation"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxWorksheetSource represents the location of the source of the data that // is stored in the cache. type xlsxWorksheetSource struct { RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` Ref string `xml:"ref,attr,omitempty"` Name string `xml:"name,attr,omitempty"` Sheet string `xml:"sheet,attr,omitempty"` } // xlsxConsolidation represents the description of the PivotCache source using // multiple consolidation ranges. This element is used when the source of the // PivotTable is a collection of ranges in the workbook. The ranges are // specified in the rangeSets collection. The logic for how the application // consolidates the data in the ranges is application- defined. type xlsxConsolidation struct{} // xlsxCacheFields represents the collection of field definitions in the // source data. type xlsxCacheFields struct { Count int `xml:"count,attr"` CacheField []*xlsxCacheField `xml:"cacheField"` } // xlsxCacheField represent a single field in the PivotCache. This definition // contains information about the field, such as its source, data type, and // location within a level or hierarchy. The sharedItems element stores // additional information about the data in this field. If there are no shared // items, then values are stored directly in the pivotCacheRecords part. type xlsxCacheField struct { Name string `xml:"name,attr"` Caption string `xml:"caption,attr,omitempty"` PropertyName string `xml:"propertyName,attr,omitempty"` ServerField bool `xml:"serverField,attr,omitempty"` UniqueList bool `xml:"uniqueList,attr,omitempty"` NumFmtID int `xml:"numFmtId,attr"` Formula string `xml:"formula,attr,omitempty"` SQLType int `xml:"sqlType,attr,omitempty"` Hierarchy int `xml:"hierarchy,attr,omitempty"` Level int `xml:"level,attr,omitempty"` DatabaseField bool `xml:"databaseField,attr,omitempty"` MappingCount int `xml:"mappingCount,attr,omitempty"` MemberPropertyField bool `xml:"memberPropertyField,attr,omitempty"` SharedItems *xlsxSharedItems `xml:"sharedItems"` FieldGroup *xlsxFieldGroup `xml:"fieldGroup"` MpMap *xlsxX `xml:"mpMap"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxSharedItems represents the collection of unique items for a field in // the PivotCacheDefinition. The sharedItems complex type stores data type and // formatting information about the data in a field. Items in the // PivotCacheDefinition can be shared in order to reduce the redundancy of // those values that are referenced in multiple places across all the // PivotTable parts. type xlsxSharedItems struct { ContainsSemiMixedTypes bool `xml:"containsSemiMixedTypes,attr,omitempty"` ContainsNonDate bool `xml:"containsNonDate,attr,omitempty"` ContainsDate bool `xml:"containsDate,attr,omitempty"` ContainsString bool `xml:"containsString,attr,omitempty"` ContainsBlank bool `xml:"containsBlank,attr,omitempty"` ContainsMixedTypes bool `xml:"containsMixedTypes,attr,omitempty"` ContainsNumber bool `xml:"containsNumber,attr,omitempty"` ContainsInteger bool `xml:"containsInteger,attr,omitempty"` MinValue float64 `xml:"minValue,attr,omitempty"` MaxValue float64 `xml:"maxValue,attr,omitempty"` MinDate string `xml:"minDate,attr,omitempty"` MaxDate string `xml:"maxDate,attr,omitempty"` Count int `xml:"count,attr"` LongText bool `xml:"longText,attr,omitempty"` M []xlsxMissing `xml:"m"` N []xlsxNumber `xml:"n"` B []xlsxBoolean `xml:"b"` E []xlsxError `xml:"e"` S []xlsxString `xml:"s"` D []xlsxDateTime `xml:"d"` } // xlsxMissing represents a value that was not specified. type xlsxMissing struct{} // xlsxNumber represents a numeric value in the PivotTable. type xlsxNumber struct { V float64 `xml:"v,attr"` U bool `xml:"u,attr,omitempty"` F bool `xml:"f,attr,omitempty"` C string `xml:"c,attr,omitempty"` Cp int `xml:"cp,attr,omitempty"` In int `xml:"in,attr,omitempty"` Bc string `xml:"bc,attr,omitempty"` Fc string `xml:"fc,attr,omitempty"` I bool `xml:"i,attr,omitempty"` Un bool `xml:"un,attr,omitempty"` St bool `xml:"st,attr,omitempty"` B bool `xml:"b,attr,omitempty"` Tpls *xlsxTuples `xml:"tpls"` X *attrValInt `xml:"x"` } // xlsxTuples represents members for the OLAP sheet data entry, also known as // a tuple. type xlsxTuples struct{} // xlsxBoolean represents a boolean value for an item in the PivotTable. type xlsxBoolean struct{} // xlsxError represents an error value. The use of this item indicates that an // error value is present in the PivotTable source. The error is recorded in // the value attribute. type xlsxError struct{} // xlsxString represents a character value in a PivotTable. type xlsxString struct { V string `xml:"v,attr"` U bool `xml:"u,attr,omitempty"` F bool `xml:"f,attr,omitempty"` C string `xml:"c,attr,omitempty"` Cp int `xml:"cp,attr,omitempty"` In int `xml:"in,attr,omitempty"` Bc string `xml:"bc,attr,omitempty"` Fc string `xml:"fc,attr,omitempty"` I bool `xml:"i,attr,omitempty"` Un bool `xml:"un,attr,omitempty"` St bool `xml:"st,attr,omitempty"` B bool `xml:"b,attr,omitempty"` Tpls *xlsxTuples `xml:"tpls"` X *attrValInt `xml:"x"` } // xlsxDateTime represents a date-time value in the PivotTable. type xlsxDateTime struct{} // xlsxFieldGroup represents the collection of properties for a field group. type xlsxFieldGroup struct{} // xlsxCacheHierarchies represents the collection of OLAP hierarchies in the // PivotCache. type xlsxCacheHierarchies struct{} // xlsxKpis represents the collection of Key Performance Indicators (KPIs) // defined on the OLAP server and stored in the PivotCache. type xlsxKpis struct{} // xlsxTupleCache represents the cache of OLAP sheet data members, or tuples. type xlsxTupleCache struct{} // xlsxCalculatedItems represents the collection of calculated items. type xlsxCalculatedItems struct{} // xlsxCalculatedMembers represents the collection of calculated members in an // OLAP PivotTable. type xlsxCalculatedMembers struct{} // xlsxDimensions represents the collection of PivotTable OLAP dimensions. type xlsxDimensions struct{} // xlsxMeasureGroups represents the collection of PivotTable OLAP measure // groups. type xlsxMeasureGroups struct{} // xlsxMaps represents the PivotTable OLAP measure group - Dimension maps. type xlsxMaps struct{} // xlsxX14PivotCacheDefinition specifies the extended properties of a pivot // table cache definition. type xlsxX14PivotCacheDefinition struct { XMLName xml.Name `xml:"x14:pivotCacheDefinition"` PivotCacheID int `xml:"pivotCacheId,attr"` } // decodeX14PivotCacheDefinition defines the structure used to parse the // x14:pivotCacheDefinition element of a pivot table cache. type decodeX14PivotCacheDefinition struct { XMLName xml.Name `xml:"pivotCacheDefinition"` PivotCacheID int `xml:"pivotCacheId,attr"` } ================================================ FILE: xmlPivotTable.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxPivotTableDefinition represents the PivotTable root element for // non-null PivotTables. There exists one pivotTableDefinition for each // PivotTableDefinition part type xlsxPivotTableDefinition struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"` Name string `xml:"name,attr"` CacheID int `xml:"cacheId,attr"` ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"` ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"` ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"` ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"` ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"` ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"` DataOnRows bool `xml:"dataOnRows,attr,omitempty"` DataPosition int `xml:"dataPosition,attr,omitempty"` DataCaption string `xml:"dataCaption,attr"` GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"` ErrorCaption string `xml:"errorCaption,attr,omitempty"` ShowError *bool `xml:"showError,attr"` MissingCaption string `xml:"missingCaption,attr,omitempty"` ShowMissing bool `xml:"showMissing,attr,omitempty"` PageStyle string `xml:"pageStyle,attr,omitempty"` PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"` VacatedStyle string `xml:"vacatedStyle,attr,omitempty"` Tag string `xml:"tag,attr,omitempty"` UpdatedVersion int `xml:"updatedVersion,attr,omitempty"` MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"` AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"` ShowItems bool `xml:"showItems,attr,omitempty"` EditData bool `xml:"editData,attr,omitempty"` DisableFieldList bool `xml:"disableFieldList,attr,omitempty"` ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"` VisualTotals bool `xml:"visualTotals,attr,omitempty"` ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"` ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"` ShowDrill *bool `xml:"showDrill,attr"` PrintDrill bool `xml:"printDrill,attr,omitempty"` ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"` ShowDataTips bool `xml:"showDataTips,attr,omitempty"` EnableWizard bool `xml:"enableWizard,attr,omitempty"` EnableDrill bool `xml:"enableDrill,attr,omitempty"` EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"` PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"` UseAutoFormatting *bool `xml:"useAutoFormatting,attr"` PageWrap int `xml:"pageWrap,attr,omitempty"` PageOverThenDown *bool `xml:"pageOverThenDown,attr"` SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"` RowGrandTotals *bool `xml:"rowGrandTotals,attr"` ColGrandTotals *bool `xml:"colGrandTotals,attr"` FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"` ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"` MergeItem *bool `xml:"mergeItem,attr"` ShowDropZones bool `xml:"showDropZones,attr,omitempty"` CreatedVersion int `xml:"createdVersion,attr,omitempty"` Indent int `xml:"indent,attr,omitempty"` ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"` ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"` ShowHeaders bool `xml:"showHeaders,attr,omitempty"` Compact *bool `xml:"compact,attr"` Outline *bool `xml:"outline,attr"` OutlineData bool `xml:"outlineData,attr,omitempty"` CompactData *bool `xml:"compactData,attr"` Published bool `xml:"published,attr,omitempty"` GridDropZones bool `xml:"gridDropZones,attr,omitempty"` Immersive bool `xml:"immersive,attr,omitempty"` MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"` ChartFormat int `xml:"chartFormat,attr,omitempty"` RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"` ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"` FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"` MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"` CustomListSort bool `xml:"customListSort,attr,omitempty"` Location *xlsxLocation `xml:"location"` PivotFields *xlsxPivotFields `xml:"pivotFields"` RowFields *xlsxRowFields `xml:"rowFields"` RowItems *xlsxRowItems `xml:"rowItems"` ColFields *xlsxColFields `xml:"colFields"` ColItems *xlsxColItems `xml:"colItems"` PageFields *xlsxPageFields `xml:"pageFields"` DataFields *xlsxDataFields `xml:"dataFields"` ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"` PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"` } // xlsxLocation represents location information for the PivotTable. type xlsxLocation struct { Ref string `xml:"ref,attr"` FirstHeaderRow int `xml:"firstHeaderRow,attr"` FirstDataRow int `xml:"firstDataRow,attr"` FirstDataCol int `xml:"firstDataCol,attr"` RowPageCount int `xml:"rowPageCount,attr,omitempty"` ColPageCount int `xml:"colPageCount,attr,omitempty"` } // xlsxPivotFields represents the collection of fields that appear on the // PivotTable. type xlsxPivotFields struct { Count int `xml:"count,attr"` PivotField []*xlsxPivotField `xml:"pivotField"` } // xlsxPivotField represents a single field in the PivotTable. This element // contains information about the field, including the collection of items in // the field. type xlsxPivotField struct { Name string `xml:"name,attr,omitempty"` Axis string `xml:"axis,attr,omitempty"` DataField bool `xml:"dataField,attr,omitempty"` SubtotalCaption string `xml:"subtotalCaption,attr,omitempty"` ShowDropDowns bool `xml:"showDropDowns,attr,omitempty"` HiddenLevel bool `xml:"hiddenLevel,attr,omitempty"` UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"` Compact *bool `xml:"compact,attr"` AllDrilled bool `xml:"allDrilled,attr,omitempty"` NumFmtID string `xml:"numFmtId,attr,omitempty"` Outline *bool `xml:"outline,attr"` SubtotalTop bool `xml:"subtotalTop,attr,omitempty"` DragToRow bool `xml:"dragToRow,attr,omitempty"` DragToCol bool `xml:"dragToCol,attr,omitempty"` MultipleItemSelectionAllowed bool `xml:"multipleItemSelectionAllowed,attr,omitempty"` DragToPage bool `xml:"dragToPage,attr,omitempty"` DragToData bool `xml:"dragToData,attr,omitempty"` DragOff bool `xml:"dragOff,attr,omitempty"` ShowAll bool `xml:"showAll,attr"` InsertBlankRow bool `xml:"insertBlankRow,attr,omitempty"` ServerField bool `xml:"serverField,attr,omitempty"` InsertPageBreak bool `xml:"insertPageBreak,attr,omitempty"` AutoShow bool `xml:"autoShow,attr,omitempty"` TopAutoShow bool `xml:"topAutoShow,attr,omitempty"` HideNewItems bool `xml:"hideNewItems,attr,omitempty"` MeasureFilter bool `xml:"measureFilter,attr,omitempty"` IncludeNewItemsInFilter bool `xml:"includeNewItemsInFilter,attr,omitempty"` ItemPageCount int `xml:"itemPageCount,attr,omitempty"` SortType string `xml:"sortType,attr,omitempty"` DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"` NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"` RankBy int `xml:"rankBy,attr,omitempty"` DefaultSubtotal *bool `xml:"defaultSubtotal,attr"` SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"` CountASubtotal bool `xml:"countASubtotal,attr,omitempty"` AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"` MaxSubtotal bool `xml:"maxSubtotal,attr,omitempty"` MinSubtotal bool `xml:"minSubtotal,attr,omitempty"` ProductSubtotal bool `xml:"productSubtotal,attr,omitempty"` CountSubtotal bool `xml:"countSubtotal,attr,omitempty"` StdDevSubtotal bool `xml:"stdDevSubtotal,attr,omitempty"` StdDevPSubtotal bool `xml:"stdDevPSubtotal,attr,omitempty"` VarSubtotal bool `xml:"varSubtotal,attr,omitempty"` VarPSubtotal bool `xml:"varPSubtotal,attr,omitempty"` ShowPropCell bool `xml:"showPropCell,attr,omitempty"` ShowPropTip bool `xml:"showPropTip,attr,omitempty"` ShowPropAsCaption bool `xml:"showPropAsCaption,attr,omitempty"` DefaultAttributeDrillState bool `xml:"defaultAttributeDrillState,attr,omitempty"` Items *xlsxItems `xml:"items"` AutoSortScope *xlsxAutoSortScope `xml:"autoSortScope"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxItems represents the collection of items in a PivotTable field. The // items in the collection are ordered by index. Items represent the unique // entries from the field in the source data. type xlsxItems struct { Count int `xml:"count,attr"` Item []*xlsxItem `xml:"item"` } // xlsxItem represents a single item in PivotTable field. type xlsxItem struct { N string `xml:"n,attr,omitempty"` T string `xml:"t,attr,omitempty"` H bool `xml:"h,attr,omitempty"` S bool `xml:"s,attr,omitempty"` SD bool `xml:"sd,attr,omitempty"` F bool `xml:"f,attr,omitempty"` M bool `xml:"m,attr,omitempty"` C bool `xml:"c,attr,omitempty"` X *int `xml:"x,attr,omitempty"` D bool `xml:"d,attr,omitempty"` E bool `xml:"e,attr,omitempty"` } // xlsxAutoSortScope represents the sorting scope for the PivotTable. type xlsxAutoSortScope struct{} // xlsxRowFields represents the collection of row fields for the PivotTable. type xlsxRowFields struct { Count int `xml:"count,attr"` Field []*xlsxField `xml:"field"` } // xlsxField represents a generic field that can appear either on the column // or the row region of the PivotTable. There areas many elements as there // are item values in any particular column or row. type xlsxField struct { X int `xml:"x,attr"` } // xlsxRowItems represents the collection of items in row axis of the // PivotTable. type xlsxRowItems struct { Count int `xml:"count,attr"` I []*xlsxI `xml:"i"` } // xlsxI represents the collection of items in the row region of the // PivotTable. type xlsxI struct { X []*xlsxX `xml:"x"` } // xlsxX represents an array of indexes to cached shared item values. type xlsxX struct{} // xlsxColFields represents the collection of fields that are on the column // axis of the PivotTable. type xlsxColFields struct { Count int `xml:"count,attr"` Field []*xlsxField `xml:"field"` } // xlsxColItems represents the collection of column items of the PivotTable. type xlsxColItems struct { Count int `xml:"count,attr"` I []*xlsxI `xml:"i"` } // xlsxPageFields represents the collection of items in the page or report // filter region of the PivotTable. type xlsxPageFields struct { Count int `xml:"count,attr"` PageField []*xlsxPageField `xml:"pageField"` } // xlsxPageField represents a field on the page or report filter of the // PivotTable. type xlsxPageField struct { Fld int `xml:"fld,attr"` Item int `xml:"item,attr,omitempty"` Hier int `xml:"hier,attr,omitempty"` Name string `xml:"name,attr,omitempty"` Cap string `xml:"cap,attr,omitempty"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxDataFields represents the collection of items in the data region of the // PivotTable. type xlsxDataFields struct { Count int `xml:"count,attr"` DataField []*xlsxDataField `xml:"dataField"` } // xlsxDataField represents a field from a source list, table, or database // that contains data that is summarized in a PivotTable. type xlsxDataField struct { Name string `xml:"name,attr,omitempty"` Fld int `xml:"fld,attr"` Subtotal string `xml:"subtotal,attr,omitempty"` ShowDataAs string `xml:"showDataAs,attr,omitempty"` BaseField int `xml:"baseField,attr,omitempty"` BaseItem int64 `xml:"baseItem,attr,omitempty"` NumFmtID int `xml:"numFmtId,attr,omitempty"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxConditionalFormats represents the collection of conditional formats // applied to a PivotTable. type xlsxConditionalFormats struct{} // xlsxPivotTableStyleInfo represent information on style applied to the // PivotTable. type xlsxPivotTableStyleInfo struct { Name string `xml:"name,attr"` ShowRowHeaders bool `xml:"showRowHeaders,attr"` ShowColHeaders bool `xml:"showColHeaders,attr"` ShowRowStripes bool `xml:"showRowStripes,attr,omitempty"` ShowColStripes bool `xml:"showColStripes,attr,omitempty"` ShowLastColumn bool `xml:"showLastColumn,attr,omitempty"` } ================================================ FILE: xmlSharedStrings.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxSST directly maps the sst element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may // be stored directly inside spreadsheet cell elements; however, storing the // same value inside multiple cell elements can result in very large worksheet // Parts, possibly resulting in performance degradation. The Shared String Table // is an indexed list of string values, shared across the workbook, which allows // implementations to store values only once. type xlsxSST struct { mu sync.Mutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"` Count int `xml:"count,attr"` UniqueCount int `xml:"uniqueCount,attr"` SI []xlsxSI `xml:"si"` } // xlsxSI (String Item) is the representation of an individual string in the // Shared String table. If the string is just a simple string with formatting // applied at the cell level, then the String Item (si) should contain a // single text element used to express the string. However, if the string in // the cell is more complex - i.e., has formatting applied at the character // level - then the string item shall consist of multiple rich text runs which // collectively are used to express the string. type xlsxSI struct { T *xlsxT `xml:"t,omitempty"` R []xlsxR `xml:"r"` RPh []*xlsxPhoneticRun `xml:"rPh"` PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` } // xlsxR represents a run of rich text. A rich text run is a region of text // that share a common set of properties, such as formatting properties. The // properties are defined in the rPr element, and the text displayed to the // user is defined in the Text (t) element. type xlsxR struct { XMLName xml.Name `xml:"r"` RPr *xlsxRPr `xml:"rPr"` T *xlsxT `xml:"t"` } // xlsxT directly maps the t element in the run properties. type xlsxT struct { XMLName xml.Name `xml:"t"` Space xml.Attr `xml:"space,attr,omitempty"` Val string `xml:",chardata"` } // xlsxRPr (Run Properties) specifies a set of run properties which shall be // applied to the contents of the parent run after all style formatting has been // applied to the text. These properties are defined as direct formatting, since // they are directly applied to the run and supersede any formatting from // styles. type xlsxRPr struct { RFont *attrValString `xml:"rFont"` Charset *attrValInt `xml:"charset"` Family *attrValInt `xml:"family"` B *attrValBool `xml:"b"` I *attrValBool `xml:"i"` Strike *attrValBool `xml:"strike"` Outline *attrValBool `xml:"outline"` Shadow *attrValBool `xml:"shadow"` Condense *attrValBool `xml:"condense"` Extend *attrValBool `xml:"extend"` Color *xlsxColor `xml:"color"` Sz *attrValFloat `xml:"sz"` U *attrValString `xml:"u"` VertAlign *attrValString `xml:"vertAlign"` Scheme *attrValString `xml:"scheme"` } // RichTextRun directly maps the settings of the rich text run. type RichTextRun struct { Font *Font Text string } ================================================ FILE: xmlSlicers.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxSlicers directly maps the slicers element that specifies a slicer view on // the worksheet. type xlsxSlicers struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicers"` XMLNSXMC string `xml:"xmlns:mc,attr"` XMLNSX string `xml:"xmlns:x,attr"` XMLNSXR10 string `xml:"xmlns:xr10,attr"` Slicer []xlsxSlicer `xml:"slicer"` } // xlsxSlicer is a complex type that specifies a slicer view. type xlsxSlicer struct { Name string `xml:"name,attr"` XR10UID string `xml:"xr10:uid,attr,omitempty"` Cache string `xml:"cache,attr"` Caption string `xml:"caption,attr,omitempty"` StartItem *int `xml:"startItem,attr"` ColumnCount *int `xml:"columnCount,attr"` ShowCaption *bool `xml:"showCaption,attr"` Level int `xml:"level,attr,omitempty"` Style string `xml:"style,attr,omitempty"` LockedPosition bool `xml:"lockedPosition,attr,omitempty"` RowHeight int `xml:"rowHeight,attr"` } // slicerCacheDefinition directly maps the slicerCacheDefinition element that // specifies a slicer cache. type xlsxSlicerCacheDefinition struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicerCacheDefinition"` XMLNSXMC string `xml:"xmlns:mc,attr"` XMLNSX string `xml:"xmlns:x,attr"` XMLNSX15 string `xml:"xmlns:x15,attr,omitempty"` XMLNSXR10 string `xml:"xmlns:xr10,attr"` Name string `xml:"name,attr"` XR10UID string `xml:"xr10:uid,attr,omitempty"` SourceName string `xml:"sourceName,attr"` PivotTables *xlsxSlicerCachePivotTables `xml:"pivotTables"` Data *xlsxSlicerCacheData `xml:"data"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxSlicerCachePivotTables is a complex type that specifies a group of // pivotTable elements that specify the PivotTable views that are filtered by // the slicer cache. type xlsxSlicerCachePivotTables struct { PivotTable []xlsxSlicerCachePivotTable `xml:"pivotTable"` } // xlsxSlicerCachePivotTable is a complex type that specifies a PivotTable view // filtered by a slicer cache. type xlsxSlicerCachePivotTable struct { TabID int `xml:"tabId,attr"` Name string `xml:"name,attr"` } // xlsxSlicerCacheData is a complex type that specifies a data source for the // slicer cache. type xlsxSlicerCacheData struct { OLAP *xlsxInnerXML `xml:"olap"` Tabular *xlsxTabularSlicerCache `xml:"tabular"` } // xlsxTabularSlicerCache is a complex type that specifies non-OLAP slicer items // that are cached within this slicer cache and properties of the slicer cache // specific to non-OLAP slicer items. type xlsxTabularSlicerCache struct { PivotCacheID int `xml:"pivotCacheId,attr"` SortOrder string `xml:"sortOrder,attr,omitempty"` CustomListSort *bool `xml:"customListSort,attr"` ShowMissing *bool `xml:"showMissing,attr"` CrossFilter string `xml:"crossFilter,attr,omitempty"` Items *xlsxTabularSlicerCacheItems `xml:"items"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxTabularSlicerCacheItems is a complex type that specifies non-OLAP slicer // items that are cached within this slicer cache. type xlsxTabularSlicerCacheItems struct { Count int `xml:"count,attr,omitempty"` I []xlsxTabularSlicerCacheItem `xml:"i"` } // xlsxTabularSlicerCacheItem is a complex type that specifies a non-OLAP slicer // item that is cached within this slicer cache. type xlsxTabularSlicerCacheItem struct { X int `xml:"x,attr"` S bool `xml:"s,attr,omitempty"` ND bool `xml:"nd,attr,omitempty"` } // xlsxTableSlicerCache specifies a table data source for the slicer cache. type xlsxTableSlicerCache struct { XMLName xml.Name `xml:"x15:tableSlicerCache"` TableID int `xml:"tableId,attr"` Column int `xml:"column,attr"` SortOrder string `xml:"sortOrder,attr,omitempty"` CustomListSort *bool `xml:"customListSort,attr"` CrossFilter string `xml:"crossFilter,attr,omitempty"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxX14SlicerList specifies a list of slicer. type xlsxX14SlicerList struct { XMLName xml.Name `xml:"x14:slicerList"` Slicer []*xlsxX14Slicer `xml:"x14:slicer"` } // xlsxX14Slicer specifies a slicer view, type xlsxX14Slicer struct { XMLName xml.Name `xml:"x14:slicer"` RID string `xml:"r:id,attr"` } // xlsxX14SlicerCaches directly maps the x14:slicerCache element. type xlsxX14SlicerCaches struct { XMLName xml.Name `xml:"x14:slicerCaches"` XMLNS string `xml:"xmlns:x14,attr"` Content string `xml:",innerxml"` } // xlsxX15SlicerCaches directly maps the x14:slicerCache element. type xlsxX14SlicerCache struct { XMLName xml.Name `xml:"x14:slicerCache"` RID string `xml:"r:id,attr"` } // xlsxX15SlicerCaches directly maps the x15:slicerCaches element. type xlsxX15SlicerCaches struct { XMLName xml.Name `xml:"x15:slicerCaches"` XMLNS string `xml:"xmlns:x14,attr"` Content string `xml:",innerxml"` } // decodeTableSlicerCache defines the structure used to parse the // x15:tableSlicerCache element of the table slicer cache. type decodeTableSlicerCache struct { XMLName xml.Name `xml:"tableSlicerCache"` TableID int `xml:"tableId,attr"` Column int `xml:"column,attr"` SortOrder string `xml:"sortOrder,attr"` } // decodeSlicerList defines the structure used to parse the x14:slicerList // element of a list of slicer. type decodeSlicerList struct { XMLName xml.Name `xml:"slicerList"` Slicer []*decodeSlicer `xml:"slicer"` } // decodeSlicer defines the structure used to parse the x14:slicer element of a // slicer. type decodeSlicer struct { RID string `xml:"id,attr"` } // decodeSlicerCaches defines the structure used to parse the // x14:slicerCaches and x15:slicerCaches element of a slicer cache. type decodeSlicerCaches struct { XMLName xml.Name `xml:"slicerCaches"` Content string `xml:",innerxml"` } // xlsxTimelines is a mechanism for filtering data in pivot table views, cube // functions and charts based on non-worksheet pivot tables. In the case of // using OLAP Timeline source data, a Timeline is based on a key attribute of // an OLAP hierarchy. In the case of using native Timeline source data, a // Timeline is based on a data table column. type xlsxTimelines struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main timelines"` XMLNSXMC string `xml:"xmlns:mc,attr"` XMLNSX string `xml:"xmlns:x,attr"` XMLNSXR10 string `xml:"xmlns:xr10,attr"` Timeline []xlsxTimeline `xml:"timeline"` } // xlsxTimeline is timeline view specifies the display of a timeline on a // worksheet. type xlsxTimeline struct { Name string `xml:"name,attr"` XR10UID string `xml:"xr10:uid,attr,omitempty"` Cache string `xml:"cache,attr"` Caption string `xml:"caption,attr,omitempty"` ShowHeader *bool `xml:"showHeader,attr"` ShowSelectionLabel *bool `xml:"showSelectionLabel,attr"` ShowTimeLevel *bool `xml:"showTimeLevel,attr"` ShowHorizontalScrollbar *bool `xml:"showHorizontalScrollbar,attr"` Level int `xml:"level,attr"` SelectionLevel int `xml:"selectionLevel,attr"` ScrollPosition string `xml:"scrollPosition,attr,omitempty"` Style string `xml:"style,attr,omitempty"` } ================================================ FILE: xmlStyles.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxStyleSheet is the root element of the Styles part. type xlsxStyleSheet struct { mu sync.Mutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"` NumFmts *xlsxNumFmts `xml:"numFmts"` Fonts *xlsxFonts `xml:"fonts"` Fills *xlsxFills `xml:"fills"` Borders *xlsxBorders `xml:"borders"` CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs"` CellXfs *xlsxCellXfs `xml:"cellXfs"` CellStyles *xlsxCellStyles `xml:"cellStyles"` Dxfs *xlsxDxfs `xml:"dxfs"` TableStyles *xlsxTableStyles `xml:"tableStyles"` Colors *xlsxStyleColors `xml:"colors"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxAlignment formatting information pertaining to text alignment in cells. // There are a variety of choices for how text is aligned both horizontally and // vertically, as well as indentation settings, and so on. type xlsxAlignment struct { Horizontal string `xml:"horizontal,attr,omitempty"` Indent int `xml:"indent,attr,omitempty"` JustifyLastLine bool `xml:"justifyLastLine,attr,omitempty"` ReadingOrder uint64 `xml:"readingOrder,attr,omitempty"` RelativeIndent int `xml:"relativeIndent,attr,omitempty"` ShrinkToFit bool `xml:"shrinkToFit,attr,omitempty"` TextRotation int `xml:"textRotation,attr,omitempty"` Vertical string `xml:"vertical,attr,omitempty"` WrapText bool `xml:"wrapText,attr,omitempty"` } // xlsxProtection (Protection Properties) contains protection properties // associated with the cell. Each cell has protection properties that can be // set. The cell protection properties do not take effect unless the sheet has // been protected. type xlsxProtection struct { Hidden *bool `xml:"hidden,attr"` Locked *bool `xml:"locked,attr"` } // xlsxLine expresses a single set of cell border. type xlsxLine struct { Style string `xml:"style,attr,omitempty"` Color *xlsxColor `xml:"color"` } // xlsxColor is a common mapping used for both the fgColor and bgColor elements. // Foreground color of the cell fill pattern. Cell fill patterns operate with // two colors: a background color and a foreground color. These combine // to make a patterned cell fill. Background color of the cell fill pattern. // Cell fill patterns operate with two colors: a background color and a // foreground color. These combine to make a patterned cell fill. type xlsxColor struct { Auto bool `xml:"auto,attr,omitempty"` RGB string `xml:"rgb,attr,omitempty"` Indexed int `xml:"indexed,attr,omitempty"` Theme *int `xml:"theme,attr"` Tint float64 `xml:"tint,attr,omitempty"` } // xlsxFonts directly maps the font element. This element contains all font // definitions for this workbook. type xlsxFonts struct { Count int `xml:"count,attr"` Font []*xlsxFont `xml:"font"` } // xlsxFont directly maps the font element. This element defines the // properties for one of the fonts used in this workbook. type xlsxFont struct { Name *attrValString `xml:"name"` Charset *attrValInt `xml:"charset"` Family *attrValInt `xml:"family"` B *attrValBool `xml:"b"` I *attrValBool `xml:"i"` Strike *attrValBool `xml:"strike"` Outline *attrValBool `xml:"outline"` Shadow *attrValBool `xml:"shadow"` Condense *attrValBool `xml:"condense"` Extend *attrValBool `xml:"extend"` Color *xlsxColor `xml:"color"` Sz *attrValFloat `xml:"sz"` U *attrValString `xml:"u"` VertAlign *attrValString `xml:"vertAlign"` Scheme *attrValString `xml:"scheme"` } // xlsxFills directly maps the fills' element. This element defines the cell // fills portion of the Styles part, consisting of a sequence of fill records. A // cell fill consists of a background color, foreground color, and pattern to be // applied across the cell. type xlsxFills struct { Count int `xml:"count,attr"` Fill []*xlsxFill `xml:"fill"` } // xlsxFill directly maps the fill element. This element specifies fill // formatting. type xlsxFill struct { PatternFill *xlsxPatternFill `xml:"patternFill"` GradientFill *xlsxGradientFill `xml:"gradientFill"` } // xlsxPatternFill is used to specify cell fill information for pattern and // solid color cell fills. For solid cell fills (no pattern), fgColor is used. // For cell fills with patterns specified, then the cell fill color is // specified by the bgColor element. type xlsxPatternFill struct { PatternType string `xml:"patternType,attr,omitempty"` FgColor *xlsxColor `xml:"fgColor"` BgColor *xlsxColor `xml:"bgColor"` } // xlsxGradientFill defines a gradient-style cell fill. Gradient cell fills can // use one or two colors as the end points of color interpolation. type xlsxGradientFill struct { Bottom float64 `xml:"bottom,attr,omitempty"` Degree float64 `xml:"degree,attr,omitempty"` Left float64 `xml:"left,attr,omitempty"` Right float64 `xml:"right,attr,omitempty"` Top float64 `xml:"top,attr,omitempty"` Type string `xml:"type,attr,omitempty"` Stop []*xlsxGradientFillStop `xml:"stop"` } // xlsxGradientFillStop directly maps the stop element. type xlsxGradientFillStop struct { Position float64 `xml:"position,attr"` Color xlsxColor `xml:"color,omitempty"` } // xlsxBorders directly maps the borders' element. This element contains borders // formatting information, specifying all border definitions for all cells in // the workbook. type xlsxBorders struct { Count int `xml:"count,attr"` Border []*xlsxBorder `xml:"border"` } // xlsxBorder directly maps the border element. Expresses a single set of cell // border formats (left, right, top, bottom, diagonal). Color is optional. When // missing, 'automatic' is implied. type xlsxBorder struct { DiagonalDown bool `xml:"diagonalDown,attr,omitempty"` DiagonalUp bool `xml:"diagonalUp,attr,omitempty"` Outline bool `xml:"outline,attr,omitempty"` Left *xlsxLine `xml:"left"` Right *xlsxLine `xml:"right"` Top *xlsxLine `xml:"top"` Bottom *xlsxLine `xml:"bottom"` Diagonal *xlsxLine `xml:"diagonal"` Vertical *xlsxLine `xml:"vertical"` Horizontal *xlsxLine `xml:"horizontal"` } // xlsxCellStyles directly maps the cellStyles element. This element contains // the named cell styles, consisting of a sequence of named style records. A // named cell style is a collection of direct or themed formatting (e.g., cell // border, cell fill, and font type/size/style) grouped together into a single // named style, and can be applied to a cell. type xlsxCellStyles struct { XMLName xml.Name `xml:"cellStyles"` Count int `xml:"count,attr"` CellStyle []*xlsxCellStyle `xml:"cellStyle"` } // xlsxCellStyle directly maps the cellStyle element. This element represents // the name and related formatting records for a named cell style in this // workbook. type xlsxCellStyle struct { XMLName xml.Name `xml:"cellStyle"` Name string `xml:"name,attr"` XfID int `xml:"xfId,attr"` BuiltInID *int `xml:"builtinId,attr"` ILevel *int `xml:"iLevel,attr"` Hidden *bool `xml:"hidden,attr"` CustomBuiltIn *bool `xml:"customBuiltin,attr"` } // xlsxCellStyleXfs directly maps the cellStyleXfs element. This element // contains the master formatting records (xf's) which define the formatting for // all named cell styles in this workbook. Master formatting records reference // individual elements of formatting (e.g., number format, font definitions, // cell fills, etc.) by specifying a zero-based index into those collections. // Master formatting records also specify whether to apply or ignore particular // aspects of formatting. type xlsxCellStyleXfs struct { Count int `xml:"count,attr"` Xf []xlsxXf `xml:"xf,omitempty"` } // xlsxXf directly maps the xf element. A single xf element describes all the // formatting for a cell. type xlsxXf struct { NumFmtID *int `xml:"numFmtId,attr"` FontID *int `xml:"fontId,attr"` FillID *int `xml:"fillId,attr"` BorderID *int `xml:"borderId,attr"` XfID *int `xml:"xfId,attr"` QuotePrefix *bool `xml:"quotePrefix,attr"` PivotButton *bool `xml:"pivotButton,attr"` ApplyNumberFormat *bool `xml:"applyNumberFormat,attr"` ApplyFont *bool `xml:"applyFont,attr"` ApplyFill *bool `xml:"applyFill,attr"` ApplyBorder *bool `xml:"applyBorder,attr"` ApplyAlignment *bool `xml:"applyAlignment,attr"` ApplyProtection *bool `xml:"applyProtection,attr"` Alignment *xlsxAlignment `xml:"alignment"` Protection *xlsxProtection `xml:"protection"` } // xlsxCellXfs directly maps the cellXfs element. This element contains the // master formatting records (xf) which define the formatting applied to cells // in this workbook. These records are the starting point for determining the // formatting for a cell. Cells in the Sheet Part reference the xf records by // zero-based index. type xlsxCellXfs struct { Count int `xml:"count,attr"` Xf []xlsxXf `xml:"xf,omitempty"` } // xlsxDxfs directly maps the dxfs element. This element contains the master // differential formatting records (dxf's) which define formatting for all // non-cell formatting in this workbook. Whereas xf records fully specify a // particular aspect of formatting (e.g., cell borders) by referencing those // formatting definitions elsewhere in the Styles part, dxf records specify // incremental (or differential) aspects of formatting directly inline within // the dxf element. The dxf formatting is to be applied on top of or in addition // to any formatting already present on the object using the dxf record. type xlsxDxfs struct { Count int `xml:"count,attr"` Dxfs []*xlsxDxf `xml:"dxf"` } // xlsxDxf directly maps the dxf element. A single dxf record, expressing // incremental formatting to be applied. type xlsxDxf struct { Font *xlsxFont `xml:"font"` NumFmt *xlsxNumFmt `xml:"numFmt"` Fill *xlsxFill `xml:"fill"` Alignment *xlsxAlignment `xml:"alignment"` Border *xlsxBorder `xml:"border"` Protection *xlsxProtection `xml:"protection"` ExtLst *xlsxPositiveSize2D `xml:"extLst"` } // xlsxTableStyles directly maps the tableStyles element. This element // represents a collection of Table style definitions for Table styles and // PivotTable styles used in this workbook. It consists of a sequence of // tableStyle records, each defining a single Table style. type xlsxTableStyles struct { Count int `xml:"count,attr"` DefaultPivotStyle string `xml:"defaultPivotStyle,attr"` DefaultTableStyle string `xml:"defaultTableStyle,attr"` TableStyles []*xlsxTableStyle `xml:"tableStyle"` } // xlsxTableStyle directly maps the tableStyle element. This element represents // a single table style definition that indicates how a spreadsheet application // should format and display a table. type xlsxTableStyle struct { Name string `xml:"name,attr,omitempty"` Pivot int `xml:"pivot,attr"` Count int `xml:"count,attr,omitempty"` Table bool `xml:"table,attr,omitempty"` TableStyleElement string `xml:",innerxml"` } // xlsxNumFmts directly maps the numFmts element. This element defines the // number formats in this workbook, consisting of a sequence of numFmt records, // where each numFmt record defines a particular number format, indicating how // to format and render the numeric value of a cell. type xlsxNumFmts struct { Count int `xml:"count,attr"` NumFmt []*xlsxNumFmt `xml:"numFmt"` } // xlsxNumFmt directly maps the numFmt element. This element specifies number // format properties which indicate how to format and render the numeric value // of a cell. type xlsxNumFmt struct { NumFmtID int `xml:"numFmtId,attr"` FormatCode string `xml:"formatCode,attr"` FormatCode16 string `xml:"http://schemas.microsoft.com/office/spreadsheetml/2015/02/main formatCode16,attr,omitempty"` } // xlsxIndexedColors directly maps the single ARGB entry for the corresponding // color index. type xlsxIndexedColors struct { RgbColor []xlsxColor `xml:"rgbColor"` } // xlsxStyleColors directly maps the colors' element. Color information // associated with this style sheet. This collection is written whenever the // legacy color palette has been modified (backwards compatibility settings) or // a custom color has been selected while using this workbook. type xlsxStyleColors struct { IndexedColors *xlsxIndexedColors `xml:"indexedColors"` MruColors *xlsxInnerXML `xml:"mruColors"` } // Alignment directly maps the alignment settings of the cells. type Alignment struct { Horizontal string Indent int JustifyLastLine bool ReadingOrder uint64 RelativeIndent int ShrinkToFit bool TextRotation int Vertical string WrapText bool } // Border directly maps the border settings of the cells. type Border struct { Type string Color string Style int } // Font directly maps the font settings of the fonts. type Font struct { Bold bool Italic bool Underline string Family string Size float64 Strike bool Color string ColorIndexed int ColorTheme *int ColorTint float64 VertAlign string Charset *int } // Fill directly maps the fill settings of the cells. type Fill struct { Type string Pattern int Color []string Shading int Transparency int } // Protection directly maps the protection settings of the cells. type Protection struct { Hidden bool Locked bool } // Style directly maps the style settings of the cells. type Style struct { Border []Border Fill Fill Font *Font Alignment *Alignment Protection *Protection NumFmt int DecimalPlaces *int CustomNumFmt *string NegRed bool } ================================================ FILE: xmlTable.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxTable directly maps the table element. A table helps organize and provide // structure to list of information in a worksheet. Tables have clearly labeled // columns, rows, and data regions. Tables make it easier for users to sort, // analyze, format, manage, add, and delete information. This element is the // root element for a table that is not a single cell XML table. type xlsxTable struct { XMLName xml.Name `xml:"table"` XMLNS string `xml:"xmlns,attr"` ID int `xml:"id,attr"` Name string `xml:"name,attr"` DisplayName string `xml:"displayName,attr,omitempty"` Comment string `xml:"comment,attr,omitempty"` Ref string `xml:"ref,attr"` TableType string `xml:"tableType,attr,omitempty"` HeaderRowCount *int `xml:"headerRowCount,attr"` InsertRow bool `xml:"insertRow,attr,omitempty"` InsertRowShift bool `xml:"insertRowShift,attr,omitempty"` TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"` TotalsRowShown *bool `xml:"totalsRowShown,attr"` Published bool `xml:"published,attr,omitempty"` HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` DataDxfID int `xml:"dataDxfId,attr,omitempty"` TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"` TableBorderDxfID int `xml:"tableBorderDxfId,attr,omitempty"` TotalsRowBorderDxfID int `xml:"totalsRowBorderDxfId,attr,omitempty"` HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"` ConnectionID int `xml:"connectionId,attr,omitempty"` AutoFilter *xlsxAutoFilter `xml:"autoFilter"` TableColumns *xlsxTableColumns `xml:"tableColumns"` TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"` } // xlsxAutoFilter temporarily hides rows based on a filter criteria, which is // applied column by column to a table of data in the worksheet. This collection // expresses AutoFilter settings. type xlsxAutoFilter struct { XMLName xml.Name `xml:"autoFilter"` Ref string `xml:"ref,attr"` FilterColumn []*xlsxFilterColumn `xml:"filterColumn"` } // xlsxFilterColumn directly maps the filterColumn element. The filterColumn // collection identifies a particular column in the AutoFilter range and // specifies filter information that has been applied to this column. If a // column in the AutoFilter range has no criteria specified, then there is no // corresponding filterColumn collection expressed for that column. type xlsxFilterColumn struct { ColID int `xml:"colId,attr"` HiddenButton bool `xml:"hiddenButton,attr,omitempty"` ShowButton bool `xml:"showButton,attr,omitempty"` CustomFilters *xlsxCustomFilters `xml:"customFilters"` Filters *xlsxFilters `xml:"filters"` ColorFilter *xlsxColorFilter `xml:"colorFilter"` DynamicFilter *xlsxDynamicFilter `xml:"dynamicFilter"` IconFilter *xlsxIconFilter `xml:"iconFilter"` Top10 *xlsxTop10 `xml:"top10"` } // xlsxCustomFilters directly maps the customFilters element. When there is more // than one custom filter criteria to apply (an 'and' or 'or' joining two // criteria), then this element groups the customFilter elements together. type xlsxCustomFilters struct { And bool `xml:"and,attr,omitempty"` CustomFilter []*xlsxCustomFilter `xml:"customFilter"` } // xlsxCustomFilter directly maps the customFilter element. A custom AutoFilter // specifies an operator and a value. There can be at most two customFilters // specified, and in that case the parent element specifies whether the two // conditions are joined by 'and' or 'or'. For any cells whose values do not // meet the specified criteria, the corresponding rows shall be hidden from view // when the filter is applied. type xlsxCustomFilter struct { Operator string `xml:"operator,attr,omitempty"` Val string `xml:"val,attr,omitempty"` } // xlsxFilters directly maps the filters (Filter Criteria) element. When // multiple values are chosen to filter by, or when a group of date values are // chosen to filter by, this element groups those criteria together. type xlsxFilters struct { Blank bool `xml:"blank,attr,omitempty"` CalendarType string `xml:"calendarType,attr,omitempty"` Filter []*xlsxFilter `xml:"filter"` DateGroupItem []*xlsxDateGroupItem `xml:"dateGroupItem"` } // xlsxFilter directly maps the filter element. This element expresses a filter // criteria value. type xlsxFilter struct { Val string `xml:"val,attr,omitempty"` } // xlsxColorFilter directly maps the colorFilter element. This element specifies // the color to filter by and whether to use the cell's fill or font color in // the filter criteria. If the cell's font or fill color does not match the // color specified in the criteria, the rows corresponding to those cells are // hidden from view. type xlsxColorFilter struct { CellColor bool `xml:"cellColor,attr"` DxfID int `xml:"dxfId,attr"` } // xlsxDynamicFilter directly maps the dynamicFilter element. This collection // specifies dynamic filter criteria. These criteria are considered dynamic // because they can change, either with the data itself (e.g., "above average") // or with the current system date (e.g., show values for "today"). For any // cells whose values do not meet the specified criteria, the corresponding rows // shall be hidden from view when the filter is applied. type xlsxDynamicFilter struct { MaxValISO string `xml:"maxValIso,attr,omitempty"` Type string `xml:"type,attr,omitempty"` Val float64 `xml:"val,attr,omitempty"` ValISO string `xml:"valIso,attr,omitempty"` } // xlsxIconFilter directly maps the iconFilter element. This element specifies // the icon set and particular icon within that set to filter by. For any cells // whose icon does not match the specified criteria, the corresponding rows // shall be hidden from view when the filter is applied. type xlsxIconFilter struct { IconID int `xml:"iconId,attr"` IconSet string `xml:"iconSet,attr,omitempty"` } // xlsxTop10 directly maps the top10 element. This element specifies the top N // (percent or number of items) to filter by. type xlsxTop10 struct { FilterVal float64 `xml:"filterVal,attr,omitempty"` Percent bool `xml:"percent,attr,omitempty"` Top bool `xml:"top,attr"` Val float64 `xml:"val,attr,omitempty"` } // xlsxDateGroupItem directly maps the dateGroupItem element. This collection is // used to express a group of dates or times which are used in an AutoFilter // criteria. [Note: See parent element for an example. end note] Values are // always written in the calendar type of the first date encountered in the // filter range, so that all subsequent dates, even when formatted or // represented by other calendar types, can be correctly compared for the // purposes of filtering. type xlsxDateGroupItem struct { DateTimeGrouping string `xml:"dateTimeGrouping,attr,omitempty"` Day int `xml:"day,attr,omitempty"` Hour int `xml:"hour,attr,omitempty"` Minute int `xml:"minute,attr,omitempty"` Month int `xml:"month,attr,omitempty"` Second int `xml:"second,attr,omitempty"` Year int `xml:"year,attr,omitempty"` } // xlsxTableColumns directly maps the element representing the collection of all // table columns for this table. type xlsxTableColumns struct { Count int `xml:"count,attr"` TableColumn []*xlsxTableColumn `xml:"tableColumn"` } // xlsxTableColumn directly maps the element representing a single column for // this table. type xlsxTableColumn struct { ID int `xml:"id,attr"` UniqueName string `xml:"uniqueName,attr,omitempty"` Name string `xml:"name,attr"` TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"` TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"` QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"` HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` DataDxfID int `xml:"dataDxfId,attr,omitempty"` TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"` } // xlsxTableStyleInfo directly maps the tableStyleInfo element. This element // describes which style is used to display this table, and specifies which // portions of the table have the style applied. type xlsxTableStyleInfo struct { Name string `xml:"name,attr,omitempty"` ShowFirstColumn bool `xml:"showFirstColumn,attr"` ShowLastColumn bool `xml:"showLastColumn,attr"` ShowRowStripes bool `xml:"showRowStripes,attr"` ShowColumnStripes bool `xml:"showColumnStripes,attr"` } // xlsxSingleXMLCells is a single cell table is generated from an XML mapping. // These really just look like regular cells to the spreadsheet user, but shall // be implemented as Tables "under the covers." type xlsxSingleXMLCells struct { XMLName xml.Name `xml:"singleXmlCells"` SingleXmlCell []xlsxSingleXMLCell `xml:"singleXmlCell"` } // xlsxSingleXMLCell is a element represents the table properties for a single // cell XML table. type xlsxSingleXMLCell struct { XMLName xml.Name `xml:"singleXmlCell"` ID int `xml:"id,attr"` R string `xml:"r,attr"` ConnectionID int `xml:"connectionId,attr"` XMLCellPr xlsxXMLCellPr `xml:"xmlCellPr"` ExtLst *xlsxInnerXML `xml:"extLst"` } // xlsxXMLCellPr is a element stores the XML properties for the cell of a single // cell xml table. type xlsxXMLCellPr struct { XMLName xml.Name `xml:"xmlCellPr"` ID int `xml:"id,attr"` UniqueName string `xml:"uniqueName,attr,omitempty"` XMLPr *xlsxInnerXML `xml:"xmlPr"` ExtLst *xlsxInnerXML `xml:"extLst"` } // Table directly maps the format settings of the table. type Table struct { tID int rID string tableXML string Range string Name string StyleName string ShowColumnStripes bool ShowFirstColumn bool ShowHeaderRow *bool ShowLastColumn bool ShowRowStripes *bool } // AutoFilterOptions directly maps the auto filter settings. type AutoFilterOptions struct { Column string Expression string } ================================================ FILE: xmlTheme.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import "encoding/xml" // xlsxTheme directly maps the theme element in the namespace // http://schemas.openxmlformats.org/drawingml/2006/main type xlsxTheme struct { XMLName xml.Name `xml:"a:theme"` XMLNSa string `xml:"xmlns:a,attr"` XMLNSr string `xml:"xmlns:r,attr"` Name string `xml:"name,attr"` ThemeElements xlsxBaseStyles `xml:"a:themeElements"` ObjectDefaults xlsxObjectDefaults `xml:"a:objectDefaults"` ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"a:extraClrSchemeLst"` CustClrLst *xlsxInnerXML `xml:"a:custClrLst"` ExtLst *xlsxExtLst `xml:"a:extLst"` } // xlsxBaseStyles defines the theme elements for a theme, and is the workhorse // of the theme. The bulk of the shared theme information that is used by a // given document is defined here. Within this complex type is defined a color // scheme, a font scheme, and a style matrix (format scheme) that defines // different formatting options for different pieces of a document. type xlsxBaseStyles struct { ClrScheme xlsxColorScheme `xml:"a:clrScheme"` FontScheme xlsxFontScheme `xml:"a:fontScheme"` FmtScheme xlsxStyleMatrix `xml:"a:fmtScheme"` ExtLst *xlsxExtLst `xml:"a:extLst"` } // xlsxCTColor holds the actual color values that are to be applied to a given // diagram and how those colors are to be applied. type xlsxCTColor struct { ScrgbClr *xlsxInnerXML `xml:"a:scrgbClr"` SrgbClr *attrValString `xml:"a:srgbClr"` HslClr *xlsxInnerXML `xml:"a:hslClr"` SysClr *xlsxSysClr `xml:"a:sysClr"` SchemeClr *xlsxInnerXML `xml:"a:schemeClr"` PrstClr *xlsxInnerXML `xml:"a:prstClr"` } // xlsxColorScheme defines a set of colors for the theme. The set of colors // consists of twelve color slots that can each hold a color of choice. type xlsxColorScheme struct { Name string `xml:"name,attr"` Dk1 xlsxCTColor `xml:"a:dk1"` Lt1 xlsxCTColor `xml:"a:lt1"` Dk2 xlsxCTColor `xml:"a:dk2"` Lt2 xlsxCTColor `xml:"a:lt2"` Accent1 xlsxCTColor `xml:"a:accent1"` Accent2 xlsxCTColor `xml:"a:accent2"` Accent3 xlsxCTColor `xml:"a:accent3"` Accent4 xlsxCTColor `xml:"a:accent4"` Accent5 xlsxCTColor `xml:"a:accent5"` Accent6 xlsxCTColor `xml:"a:accent6"` Hlink xlsxCTColor `xml:"a:hlink"` FolHlink xlsxCTColor `xml:"a:folHlink"` ExtLst *xlsxExtLst `xml:"a:extLst"` } // objectDefaults element allows for the definition of default shape, line, // and textbox formatting properties. An application can use this information // to format a shape (or text) initially on insertion into a document. type xlsxObjectDefaults struct { ObjectDefaults string `xml:",innerxml"` } // xlsxExtraClrSchemeLst element is a container for the list of extra color // schemes present in a document. type xlsxExtraClrSchemeLst struct { ExtraClrSchemeLst string `xml:",innerxml"` } // xlsxCTSupplementalFont defines an additional font that is used for language // specific fonts in themes. For example, one can specify a font that gets used // only within the Japanese language context. type xlsxCTSupplementalFont struct { Script string `xml:"script,attr"` Typeface string `xml:"typeface,attr"` } // xlsxFontCollection defines a major and minor font which is used in the font // scheme. A font collection consists of a font definition for Latin, East // Asian, and complex script. On top of these three definitions, one can also // define a font for use in a specific language or languages. type xlsxFontCollection struct { Latin *xlsxCTTextFont `xml:"a:latin"` Ea *xlsxCTTextFont `xml:"a:ea"` Cs *xlsxCTTextFont `xml:"a:cs"` Font []xlsxCTSupplementalFont `xml:"a:font"` ExtLst *xlsxExtLst `xml:"a:extLst"` } // xlsxFontScheme element defines the font scheme within the theme. The font // scheme consists of a pair of major and minor fonts for which to use in a // document. The major font corresponds well with the heading areas of a // document, and the minor font corresponds well with the normal text or // paragraph areas. type xlsxFontScheme struct { Name string `xml:"name,attr"` MajorFont xlsxFontCollection `xml:"a:majorFont"` MinorFont xlsxFontCollection `xml:"a:minorFont"` ExtLst *xlsxExtLst `xml:"a:extLst"` } // xlsxStyleMatrix defines a set of formatting options, which can be referenced // by documents that apply a certain style to a given part of an object. For // example, in a given shape, say a rectangle, one can reference a themed line // style, themed effect, and themed fill that would be theme specific and // change when the theme is changed. type xlsxStyleMatrix struct { Name string `xml:"name,attr,omitempty"` FillStyleLst xlsxFillStyleLst `xml:"a:fillStyleLst"` LnStyleLst xlsxLnStyleLst `xml:"a:lnStyleLst"` EffectStyleLst xlsxEffectStyleLst `xml:"a:effectStyleLst"` BgFillStyleLst xlsxBgFillStyleLst `xml:"a:bgFillStyleLst"` } // xlsxFillStyleLst element defines a set of three fill styles that are used // within a theme. The three fill styles are arranged in order from subtle to // moderate to intense. type xlsxFillStyleLst struct { FillStyleLst string `xml:",innerxml"` } // xlsxLnStyleLst element defines a list of three line styles for use within a // theme. The three line styles are arranged in order from subtle to moderate // to intense versions of lines. This list makes up part of the style matrix. type xlsxLnStyleLst struct { LnStyleLst string `xml:",innerxml"` } // xlsxEffectStyleLst element defines a set of three effect styles that create // the effect style list for a theme. The effect styles are arranged in order // of subtle to moderate to intense. type xlsxEffectStyleLst struct { EffectStyleLst string `xml:",innerxml"` } // xlsxBgFillStyleLst element defines a list of background fills that are // used within a theme. The background fills consist of three fills, arranged // in order from subtle to moderate to intense. type xlsxBgFillStyleLst struct { BgFillStyleLst string `xml:",innerxml"` } // xlsxSysClr element specifies a color bound to predefined operating system // elements. type xlsxSysClr struct { Val string `xml:"val,attr"` LastClr string `xml:"lastClr,attr"` } // decodeTheme defines the structure used to parse the a:theme element for the // theme. type decodeTheme struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"` Name string `xml:"name,attr"` ThemeElements decodeBaseStyles `xml:"themeElements"` ObjectDefaults xlsxObjectDefaults `xml:"objectDefaults"` ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"` CustClrLst *xlsxInnerXML `xml:"custClrLst"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeBaseStyles defines the structure used to parse the theme elements for a // theme, and is the workhorse of the theme. type decodeBaseStyles struct { ClrScheme decodeColorScheme `xml:"clrScheme"` FontScheme decodeFontScheme `xml:"fontScheme"` FmtScheme decodeStyleMatrix `xml:"fmtScheme"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeColorScheme defines the structure used to parse a set of colors for the // theme. type decodeColorScheme struct { Name string `xml:"name,attr"` Dk1 decodeCTColor `xml:"dk1"` Lt1 decodeCTColor `xml:"lt1"` Dk2 decodeCTColor `xml:"dk2"` Lt2 decodeCTColor `xml:"lt2"` Accent1 decodeCTColor `xml:"accent1"` Accent2 decodeCTColor `xml:"accent2"` Accent3 decodeCTColor `xml:"accent3"` Accent4 decodeCTColor `xml:"accent4"` Accent5 decodeCTColor `xml:"accent5"` Accent6 decodeCTColor `xml:"accent6"` Hlink decodeCTColor `xml:"hlink"` FolHlink decodeCTColor `xml:"folHlink"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeFontScheme defines the structure used to parse font scheme within the // theme. type decodeFontScheme struct { Name string `xml:"name,attr"` MajorFont decodeFontCollection `xml:"majorFont"` MinorFont decodeFontCollection `xml:"minorFont"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeFontCollection defines the structure used to parse a major and minor // font which is used in the font scheme. type decodeFontCollection struct { Latin *xlsxCTTextFont `xml:"latin"` Ea *xlsxCTTextFont `xml:"ea"` Cs *xlsxCTTextFont `xml:"cs"` Font []xlsxCTSupplementalFont `xml:"font"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeCTColor defines the structure used to parse the actual color values // that are to be applied to a given diagram and how those colors are to be // applied. type decodeCTColor struct { ScrgbClr *xlsxInnerXML `xml:"scrgbClr"` SrgbClr *attrValString `xml:"srgbClr"` HslClr *xlsxInnerXML `xml:"hslClr"` SysClr *xlsxSysClr `xml:"sysClr"` SchemeClr *xlsxInnerXML `xml:"schemeClr"` PrstClr *xlsxInnerXML `xml:"prstClr"` } // decodeStyleMatrix defines the structure used to parse a set of formatting // options, which can be referenced by documents that apply a certain style to // a given part of an object. type decodeStyleMatrix struct { Name string `xml:"name,attr,omitempty"` FillStyleLst xlsxFillStyleLst `xml:"fillStyleLst"` LnStyleLst xlsxLnStyleLst `xml:"lnStyleLst"` EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"` BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"` } ================================================ FILE: xmlWorkbook.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxRelationships describe references from parts to other internal resources // in the package or to external resources. type xlsxRelationships struct { mu sync.Mutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"` Relationships []xlsxRelationship `xml:"Relationship"` } // xlsxRelationship contains relations which maps id and XML. type xlsxRelationship struct { ID string `xml:"Id,attr"` Target string `xml:",attr"` Type string `xml:",attr"` TargetMode string `xml:",attr,omitempty"` } // xlsxWorkbook contains elements and attributes that encompass the data // content of the workbook. The workbook's child elements each have their own // subclause references. type xlsxWorkbook struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"` Conformance string `xml:"conformance,attr,omitempty"` FileVersion *xlsxFileVersion `xml:"fileVersion"` FileSharing *xlsxExtLst `xml:"fileSharing"` WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` BookViews *xlsxBookViews `xml:"bookViews"` Sheets xlsxSheets `xml:"sheets"` FunctionGroups *xlsxFunctionGroups `xml:"functionGroups"` ExternalReferences *xlsxExternalReferences `xml:"externalReferences"` DefinedNames *xlsxDefinedNames `xml:"definedNames"` CalcPr *xlsxCalcPr `xml:"calcPr"` OleSize *xlsxExtLst `xml:"oleSize"` CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"` PivotCaches *xlsxPivotCaches `xml:"pivotCaches"` SmartTagPr *xlsxExtLst `xml:"smartTagPr"` SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"` WebPublishing *xlsxExtLst `xml:"webPublishing"` FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"` WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxFileRecoveryPr maps sheet recovery information. This element defines // properties that track the state of the workbook file, such as whether the // file was saved during a crash, or whether it should be opened in auto-recover // mode. type xlsxFileRecoveryPr struct { AutoRecover bool `xml:"autoRecover,attr,omitempty"` CrashSave bool `xml:"crashSave,attr,omitempty"` DataExtractLoad bool `xml:"dataExtractLoad,attr,omitempty"` RepairLoad bool `xml:"repairLoad,attr,omitempty"` } // xlsxWorkbookProtection directly maps the workbookProtection element. This // element specifies options for protecting data in the workbook. Applications // might use workbook protection to prevent anyone from accidentally changing, // moving, or deleting important data. This protection can be ignored by // applications which choose not to support this optional protection mechanism. // When a password is to be hashed and stored in this element, it shall be // hashed as defined below, starting from a UTF-16LE encoded string value. If // there is a leading BOM character (U+FEFF) in the encoded password it is // removed before hash calculation. type xlsxWorkbookProtection struct { LockRevision bool `xml:"lockRevision,attr,omitempty"` LockStructure bool `xml:"lockStructure,attr,omitempty"` LockWindows bool `xml:"lockWindows,attr,omitempty"` RevisionsAlgorithmName string `xml:"revisionsAlgorithmName,attr,omitempty"` RevisionsHashValue string `xml:"revisionsHashValue,attr,omitempty"` RevisionsSaltValue string `xml:"revisionsSaltValue,attr,omitempty"` RevisionsSpinCount int `xml:"revisionsSpinCount,attr,omitempty"` WorkbookAlgorithmName string `xml:"workbookAlgorithmName,attr,omitempty"` WorkbookHashValue string `xml:"workbookHashValue,attr,omitempty"` WorkbookSaltValue string `xml:"workbookSaltValue,attr,omitempty"` WorkbookSpinCount int `xml:"workbookSpinCount,attr,omitempty"` } // xlsxFileVersion directly maps the fileVersion element. This element defines // properties that track which version of the application accessed the data and // source code contained in the file. type xlsxFileVersion struct { AppName string `xml:"appName,attr,omitempty"` CodeName string `xml:"codeName,attr,omitempty"` LastEdited string `xml:"lastEdited,attr,omitempty"` LowestEdited string `xml:"lowestEdited,attr,omitempty"` RupBuild string `xml:"rupBuild,attr,omitempty"` } // xlsxWorkbookPr directly maps the workbookPr element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main This element // defines a collection of workbook properties. type xlsxWorkbookPr struct { Date1904 bool `xml:"date1904,attr,omitempty"` ShowObjects string `xml:"showObjects,attr,omitempty"` ShowBorderUnselectedTables *bool `xml:"showBorderUnselectedTables,attr"` FilterPrivacy bool `xml:"filterPrivacy,attr,omitempty"` PromptedSolutions bool `xml:"promptedSolutions,attr,omitempty"` ShowInkAnnotation *bool `xml:"showInkAnnotation,attr"` BackupFile bool `xml:"backupFile,attr,omitempty"` SaveExternalLinkValues *bool `xml:"saveExternalLinkValues,attr"` UpdateLinks string `xml:"updateLinks,attr,omitempty"` CodeName string `xml:"codeName,attr,omitempty"` HidePivotFieldList bool `xml:"hidePivotFieldList,attr,omitempty"` ShowPivotChartFilter bool `xml:"showPivotChartFilter,attr,omitempty"` AllowRefreshQuery bool `xml:"allowRefreshQuery,attr,omitempty"` PublishItems bool `xml:"publishItems,attr,omitempty"` CheckCompatibility bool `xml:"checkCompatibility,attr,omitempty"` AutoCompressPictures *bool `xml:"autoCompressPictures,attr"` RefreshAllConnections bool `xml:"refreshAllConnections,attr,omitempty"` DefaultThemeVersion string `xml:"defaultThemeVersion,attr,omitempty"` } // xlsxBookViews directly maps the bookViews element. This element specifies the // collection of workbook views of the enclosing workbook. Each view can specify // a window position, filter options, and other configurations. There is no // limit on the number of workbook views that can be defined for a workbook. type xlsxBookViews struct { WorkBookView []xlsxWorkBookView `xml:"workbookView"` } // xlsxWorkBookView directly maps the workbookView element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main This element // specifies a single Workbook view. type xlsxWorkBookView struct { Visibility string `xml:"visibility,attr,omitempty"` Minimized bool `xml:"minimized,attr,omitempty"` ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"` ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"` ShowSheetTabs *bool `xml:"showSheetTabs,attr"` XWindow string `xml:"xWindow,attr,omitempty"` YWindow string `xml:"yWindow,attr,omitempty"` WindowWidth int `xml:"windowWidth,attr,omitempty"` WindowHeight int `xml:"windowHeight,attr,omitempty"` TabRatio float64 `xml:"tabRatio,attr,omitempty"` FirstSheet int `xml:"firstSheet,attr,omitempty"` ActiveTab int `xml:"activeTab,attr,omitempty"` AutoFilterDateGrouping *bool `xml:"autoFilterDateGrouping,attr"` } // xlsxSheets directly maps the sheets element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. type xlsxSheets struct { Sheet []xlsxSheet `xml:"sheet"` } // xlsxSheet defines a sheet in this workbook. Sheet data is stored in a // separate part. type xlsxSheet struct { Name string `xml:"name,attr,omitempty"` SheetID int `xml:"sheetId,attr,omitempty"` ID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr"` State string `xml:"state,attr,omitempty"` } // xlsxFunctionGroup represents a single function group. type xlsxFunctionGroup struct { Name string `xml:"name,attr"` } // xlsxFunctionGroups defines the collection of function groups for the workbook. type xlsxFunctionGroups struct { BuiltInGroupCount *int `xml:"builtInGroupCount,attr"` FunctionGroup []xlsxFunctionGroup `xml:"functionGroup"` } // xlsxExternalReferences directly maps the externalReferences element of the // external workbook references part. type xlsxExternalReferences struct { ExternalReference []xlsxExternalReference `xml:"externalReference"` } // xlsxExternalReference directly maps the externalReference element of the // external workbook references part. type xlsxExternalReference struct { RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxPivotCaches element enumerates pivot cache definition parts used by pivot // tables and formulas in this workbook. type xlsxPivotCaches struct { PivotCache []xlsxPivotCache `xml:"pivotCache"` } // xlsxPivotCache directly maps the pivotCache element. type xlsxPivotCache struct { CacheID int `xml:"cacheId,attr"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // extLst element provides a convention for extending spreadsheetML in // predefined locations. The locations shall be denoted with the extLst element, // and are called extension lists. Extension list locations within the markup // document are specified in the markup specification and can be used to store // extensions to the markup specification, whether those are future version // extensions of the markup specification or are private extensions implemented // independently of the markup specification. Markup within an extension might // not be understood by a consumer. type xlsxExtLst struct { Ext string `xml:",innerxml"` } // xlsxExt represents a the future feature data storage area. Each extension // within an extension list shall be contained within an ext element. // Extensions shall be versioned by namespace, using the uri attribute, and // shall be allowed to appear in any order within the extension list. Any // number of extensions shall be allowed within an extension list. type xlsxExt struct { XMLName xml.Name `xml:"ext"` URI string `xml:"uri,attr"` Content string `xml:",innerxml"` xmlns []xml.Attr } // xlsxAlternateContent is a container for a sequence of multiple // representations of a given piece of content. The program reading the file // should only process one of these, and the one chosen should be based on // which conditions match. type xlsxAlternateContent struct { XMLNSMC string `xml:"xmlns:mc,attr,omitempty"` Content string `xml:",innerxml"` } // xlsxChoice element shall be an element in the Markup Compatibility namespace // with local name "Choice". Parent elements of Choice elements shall be // AlternateContent elements. type xlsxChoice struct { XMLName xml.Name `xml:"mc:Choice"` XMLNSA14 string `xml:"xmlns:a14,attr,omitempty"` XMLNSSle15 string `xml:"xmlns:sle15,attr,omitempty"` Requires string `xml:"Requires,attr,omitempty"` Content string `xml:",innerxml"` } // xlsxFallback element shall be an element in the Markup Compatibility // namespace with local name "Fallback". Parent elements of Fallback elements // shall be AlternateContent elements. type xlsxFallback struct { XMLName xml.Name `xml:"mc:Fallback"` Content string `xml:",innerxml"` } // xlsxInnerXML holds parts of XML content currently not unmarshal. type xlsxInnerXML struct { Content string `xml:",innerxml"` } // decodeExtLst defines the structure used to parse the extLst element // of the future feature data storage area. type decodeExtLst struct { XMLName xml.Name `xml:"extLst"` Ext []*xlsxExt `xml:"ext"` } // decodeExt defines the structure used to parse the ext element. type decodeExt struct { URI string `xml:"uri,attr,omitempty"` Content string `xml:",innerxml"` } // xlsxDefinedNames directly maps the definedNames element. This element defines // the collection of defined names for this workbook. Defined names are // descriptive names to represent cells, ranges of cells, formulas, or constant // values. Defined names can be used to represent a range on any worksheet. type xlsxDefinedNames struct { DefinedName []xlsxDefinedName `xml:"definedName"` } // xlsxDefinedName directly maps the definedName element from the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main This element // defines a defined name within this workbook. A defined name is descriptive // text that is used to represent a cell, range of cells, formula, or constant // value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname type xlsxDefinedName struct { Comment string `xml:"comment,attr,omitempty"` CustomMenu string `xml:"customMenu,attr,omitempty"` Description string `xml:"description,attr,omitempty"` Function bool `xml:"function,attr,omitempty"` FunctionGroupID int `xml:"functionGroupId,attr,omitempty"` Help string `xml:"help,attr,omitempty"` Hidden bool `xml:"hidden,attr,omitempty"` LocalSheetID *int `xml:"localSheetId,attr"` Name string `xml:"name,attr,omitempty"` PublishToServer bool `xml:"publishToServer,attr,omitempty"` ShortcutKey string `xml:"shortcutKey,attr,omitempty"` StatusBar string `xml:"statusBar,attr,omitempty"` VbProcedure bool `xml:"vbProcedure,attr,omitempty"` WorkbookParameter bool `xml:"workbookParameter,attr,omitempty"` Xlm bool `xml:"xml,attr,omitempty"` Data string `xml:",chardata"` } // xlsxCalcPr directly maps the calcPr element. This element defines the // collection of properties the application uses to record calculation status // and details. Calculation is the process of computing formulas and then // displaying the results as values in the cells that contain the formulas. type xlsxCalcPr struct { CalcCompleted bool `xml:"calcCompleted,attr,omitempty"` CalcID int `xml:"calcId,attr,omitempty"` CalcMode string `xml:"calcMode,attr,omitempty"` CalcOnSave bool `xml:"calcOnSave,attr,omitempty"` ConcurrentCalc *bool `xml:"concurrentCalc,attr"` ConcurrentManualCount int `xml:"concurrentManualCount,attr,omitempty"` ForceFullCalc bool `xml:"forceFullCalc,attr,omitempty"` FullCalcOnLoad bool `xml:"fullCalcOnLoad,attr,omitempty"` FullPrecision bool `xml:"fullPrecision,attr,omitempty"` Iterate bool `xml:"iterate,attr,omitempty"` IterateCount int `xml:"iterateCount,attr,omitempty"` IterateDelta float64 `xml:"iterateDelta,attr,omitempty"` RefMode string `xml:"refMode,attr,omitempty"` } // xlsxCustomWorkbookViews defines the collection of custom workbook views that // are defined for this workbook. A customWorkbookView is similar in concept to // a workbookView in that its attributes contain settings related to the way // that the workbook should be displayed on a screen by a spreadsheet // application. type xlsxCustomWorkbookViews struct { CustomWorkbookView []xlsxCustomWorkbookView `xml:"customWorkbookView"` } // xlsxCustomWorkbookView directly maps the customWorkbookView element. This // element specifies a single custom workbook view. A custom workbook view // consists of a set of display and print settings that you can name and apply // to a workbook. You can create more than one custom workbook view of the same // workbook. Custom Workbook Views are not required in order to construct a // valid SpreadsheetML document, and are not necessary if the document is never // displayed by a spreadsheet application, or if the spreadsheet application has // a fixed display for workbooks. However, if a spreadsheet application chooses // to implement configurable display modes, the customWorkbookView element // should be used to persist the settings for those display modes. type xlsxCustomWorkbookView struct { ActiveSheetID *int `xml:"activeSheetId,attr"` AutoUpdate *bool `xml:"autoUpdate,attr"` ChangesSavedWin *bool `xml:"changesSavedWin,attr"` GUID *string `xml:"guid,attr"` IncludeHiddenRowCol *bool `xml:"includeHiddenRowCol,attr"` IncludePrintSettings *bool `xml:"includePrintSettings,attr"` Maximized *bool `xml:"maximized,attr"` MergeInterval int `xml:"mergeInterval,attr"` Minimized *bool `xml:"minimized,attr"` Name *string `xml:"name,attr"` OnlySync *bool `xml:"onlySync,attr"` PersonalView *bool `xml:"personalView,attr"` ShowComments *string `xml:"showComments,attr"` ShowFormulaBar *bool `xml:"showFormulaBar,attr"` ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"` ShowObjects *string `xml:"showObjects,attr"` ShowSheetTabs *bool `xml:"showSheetTabs,attr"` ShowStatusbar *bool `xml:"showStatusbar,attr"` ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"` TabRatio *float64 `xml:"tabRatio,attr"` WindowHeight *int `xml:"windowHeight,attr"` WindowWidth *int `xml:"windowWidth,attr"` XWindow *int `xml:"xWindow,attr"` YWindow *int `xml:"yWindow,attr"` } // DefinedName directly maps the name for a cell or cell range on a // worksheet. type DefinedName struct { Name string Comment string RefersTo string Scope string } // CalcPropsOptions defines the collection of properties the application uses to // record calculation status and details. type CalcPropsOptions struct { CalcID *uint CalcMode *string FullCalcOnLoad *bool RefMode *string Iterate *bool IterateCount *uint IterateDelta *float64 FullPrecision *bool CalcCompleted *bool CalcOnSave *bool ConcurrentCalc *bool ConcurrentManualCount *uint ForceFullCalc *bool } // WorkbookPropsOptions directly maps the settings of workbook proprieties. type WorkbookPropsOptions struct { Date1904 *bool FilterPrivacy *bool CodeName *string } // WorkbookProtectionOptions directly maps the settings of workbook protection. type WorkbookProtectionOptions struct { AlgorithmName string Password string LockStructure bool LockWindows bool } ================================================ FILE: xmlWorksheet.go ================================================ // Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.25.0 or later. package excelize import ( "encoding/xml" "sync" ) // xlsxWorksheet directly maps the worksheet element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. type xlsxWorksheet struct { mu sync.Mutex formulaSI sync.Map XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"` SheetPr *xlsxSheetPr `xml:"sheetPr"` Dimension *xlsxDimension `xml:"dimension"` SheetViews *xlsxSheetViews `xml:"sheetViews"` SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"` Cols *xlsxCols `xml:"cols"` SheetData xlsxSheetData `xml:"sheetData"` SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"` SheetProtection *xlsxSheetProtection `xml:"sheetProtection"` ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"` Scenarios *xlsxInnerXML `xml:"scenarios"` AutoFilter *xlsxAutoFilter `xml:"autoFilter"` SortState *xlsxSortState `xml:"sortState"` DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"` CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"` MergeCells *xlsxMergeCells `xml:"mergeCells"` PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"` DataValidations *xlsxDataValidations `xml:"dataValidations"` Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"` PrintOptions *xlsxPrintOptions `xml:"printOptions"` PageMargins *xlsxPageMargins `xml:"pageMargins"` PageSetUp *xlsxPageSetUp `xml:"pageSetup"` HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` RowBreaks *xlsxRowBreaks `xml:"rowBreaks"` ColBreaks *xlsxColBreaks `xml:"colBreaks"` CustomProperties *xlsxInnerXML `xml:"customProperties"` CellWatches *xlsxInnerXML `xml:"cellWatches"` IgnoredErrors *xlsxIgnoredErrors `xml:"ignoredErrors"` SmartTags *xlsxInnerXML `xml:"smartTags"` Drawing *xlsxDrawing `xml:"drawing"` LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"` LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"` DrawingHF *xlsxDrawingHF `xml:"drawingHF"` Picture *xlsxPicture `xml:"picture"` OleObjects *xlsxInnerXML `xml:"oleObjects"` Controls *xlsxInnerXML `xml:"controls"` WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` TableParts *xlsxTableParts `xml:"tableParts"` ExtLst *xlsxExtLst `xml:"extLst"` DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` } // xlsxDrawing change r:id to rid in the namespace. type xlsxDrawing struct { XMLName xml.Name `xml:"drawing"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxHeaderFooter directly maps the headerFooter element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - When printed or // viewed in page layout view (§18.18.69), each page of a worksheet can have a // page header, a page footer, or both. The headers and footers on odd-numbered // pages can differ from those on even-numbered pages, and the headers and // footers on the first page can differ from those on odd- and even-numbered // pages. In the latter case, the first page is not considered an odd page. type xlsxHeaderFooter struct { XMLName xml.Name `xml:"headerFooter"` DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"` DifferentFirst bool `xml:"differentFirst,attr,omitempty"` ScaleWithDoc *bool `xml:"scaleWithDoc,attr"` AlignWithMargins *bool `xml:"alignWithMargins,attr"` OddHeader string `xml:"oddHeader,omitempty"` OddFooter string `xml:"oddFooter,omitempty"` EvenHeader string `xml:"evenHeader,omitempty"` EvenFooter string `xml:"evenFooter,omitempty"` FirstHeader string `xml:"firstHeader,omitempty"` FirstFooter string `xml:"firstFooter,omitempty"` } // xlsxDrawingHF (Drawing Reference in Header Footer) specifies the usage of // drawing objects to be rendered in the headers and footers of the sheet. It // specifies an explicit relationship to the part containing the DrawingML // shapes used in the headers and footers. It also indicates where in the // headers and footers each shape belongs. One drawing object can appear in // each of the left section, center section and right section of a header and // a footer. type xlsxDrawingHF struct { Content string `xml:",innerxml"` } // xlsxPageSetUp directly maps the pageSetup element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup // settings for the worksheet. type xlsxPageSetUp struct { XMLName xml.Name `xml:"pageSetup"` BlackAndWhite bool `xml:"blackAndWhite,attr,omitempty"` CellComments string `xml:"cellComments,attr,omitempty"` Copies int `xml:"copies,attr,omitempty"` Draft bool `xml:"draft,attr,omitempty"` Errors string `xml:"errors,attr,omitempty"` FirstPageNumber string `xml:"firstPageNumber,attr,omitempty"` FitToHeight *int `xml:"fitToHeight,attr"` FitToWidth *int `xml:"fitToWidth,attr"` HorizontalDPI string `xml:"horizontalDpi,attr,omitempty"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` Orientation string `xml:"orientation,attr,omitempty"` PageOrder string `xml:"pageOrder,attr,omitempty"` PaperHeight string `xml:"paperHeight,attr,omitempty"` PaperSize *int `xml:"paperSize,attr"` PaperWidth string `xml:"paperWidth,attr,omitempty"` Scale int `xml:"scale,attr,omitempty"` UseFirstPageNumber bool `xml:"useFirstPageNumber,attr,omitempty"` UsePrinterDefaults bool `xml:"usePrinterDefaults,attr,omitempty"` VerticalDPI string `xml:"verticalDpi,attr,omitempty"` } // xlsxPrintOptions directly maps the printOptions element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Print options for // the sheet. Printer-specific settings are stored separately in the Printer // Settings part. type xlsxPrintOptions struct { XMLName xml.Name `xml:"printOptions"` GridLines bool `xml:"gridLines,attr,omitempty"` GridLinesSet bool `xml:"gridLinesSet,attr,omitempty"` Headings bool `xml:"headings,attr,omitempty"` HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"` VerticalCentered bool `xml:"verticalCentered,attr,omitempty"` } // xlsxPageMargins directly maps the pageMargins element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page margins for // a sheet or a custom sheet view. type xlsxPageMargins struct { XMLName xml.Name `xml:"pageMargins"` Left float64 `xml:"left,attr"` Right float64 `xml:"right,attr"` Top float64 `xml:"top,attr"` Bottom float64 `xml:"bottom,attr"` Header float64 `xml:"header,attr"` Footer float64 `xml:"footer,attr"` } // xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. This element // specifies the sheet formatting properties. type xlsxSheetFormatPr struct { XMLName xml.Name `xml:"sheetFormatPr"` BaseColWidth uint8 `xml:"baseColWidth,attr,omitempty"` DefaultColWidth float64 `xml:"defaultColWidth,attr,omitempty"` DefaultRowHeight float64 `xml:"defaultRowHeight,attr"` CustomHeight bool `xml:"customHeight,attr,omitempty"` ZeroHeight bool `xml:"zeroHeight,attr,omitempty"` ThickTop bool `xml:"thickTop,attr,omitempty"` ThickBottom bool `xml:"thickBottom,attr,omitempty"` OutlineLevelRow uint8 `xml:"outlineLevelRow,attr,omitempty"` OutlineLevelCol uint8 `xml:"outlineLevelCol,attr,omitempty"` } // xlsxSheetViews represents worksheet views collection. type xlsxSheetViews struct { XMLName xml.Name `xml:"sheetViews"` SheetView []xlsxSheetView `xml:"sheetView"` } // xlsxSheetView represents a single sheet view definition. When more than one // sheet view is defined in the file, it means that when opening the workbook, // each sheet view corresponds to a separate window within the spreadsheet // application, where each window is showing the particular sheet containing // the same workbookViewId value, the last sheetView definition is loaded, and // the others are discarded. When multiple windows are viewing the same sheet, // multiple sheetView elements (with corresponding workbookView entries) are // saved. type xlsxSheetView struct { WindowProtection bool `xml:"windowProtection,attr,omitempty"` ShowFormulas bool `xml:"showFormulas,attr,omitempty"` ShowGridLines *bool `xml:"showGridLines,attr"` ShowRowColHeaders *bool `xml:"showRowColHeaders,attr"` ShowZeros *bool `xml:"showZeros,attr,omitempty"` RightToLeft bool `xml:"rightToLeft,attr,omitempty"` TabSelected bool `xml:"tabSelected,attr,omitempty"` ShowRuler *bool `xml:"showRuler,attr,omitempty"` ShowWhiteSpace *bool `xml:"showWhiteSpace,attr"` ShowOutlineSymbols bool `xml:"showOutlineSymbols,attr,omitempty"` DefaultGridColor *bool `xml:"defaultGridColor,attr"` View string `xml:"view,attr,omitempty"` TopLeftCell string `xml:"topLeftCell,attr,omitempty"` ColorID int `xml:"colorId,attr,omitempty"` ZoomScale float64 `xml:"zoomScale,attr,omitempty"` ZoomScaleNormal float64 `xml:"zoomScaleNormal,attr,omitempty"` ZoomScalePageLayoutView float64 `xml:"zoomScalePageLayoutView,attr,omitempty"` ZoomScaleSheetLayoutView float64 `xml:"zoomScaleSheetLayoutView,attr,omitempty"` WorkbookViewID int `xml:"workbookViewId,attr"` Pane *xlsxPane `xml:"pane,omitempty"` Selection []*xlsxSelection `xml:"selection"` } // xlsxSelection directly maps the selection element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet view // selection. type xlsxSelection struct { ActiveCell string `xml:"activeCell,attr,omitempty"` ActiveCellID *int `xml:"activeCellId,attr"` Pane string `xml:"pane,attr,omitempty"` SQRef string `xml:"sqref,attr,omitempty"` } // xlsxSelection directly maps the selection element. Worksheet view pane. type xlsxPane struct { ActivePane string `xml:"activePane,attr,omitempty"` State string `xml:"state,attr,omitempty"` // Either "split" or "frozen" TopLeftCell string `xml:"topLeftCell,attr,omitempty"` XSplit float64 `xml:"xSplit,attr,omitempty"` YSplit float64 `xml:"ySplit,attr,omitempty"` } // xlsxSheetPr directly maps the sheetPr element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Sheet-level // properties. type xlsxSheetPr struct { XMLName xml.Name `xml:"sheetPr"` SyncHorizontal bool `xml:"syncHorizontal,attr,omitempty"` SyncVertical bool `xml:"syncVertical,attr,omitempty"` SyncRef string `xml:"syncRef,attr,omitempty"` TransitionEvaluation bool `xml:"transitionEvaluation,attr,omitempty"` TransitionEntry bool `xml:"transitionEntry,attr,omitempty"` Published *bool `xml:"published,attr"` CodeName string `xml:"codeName,attr,omitempty"` FilterMode bool `xml:"filterMode,attr,omitempty"` EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"` TabColor *xlsxColor `xml:"tabColor"` OutlinePr *xlsxOutlinePr `xml:"outlinePr"` PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr"` } // xlsxOutlinePr maps to the outlinePr element. SummaryBelow allows you to // adjust the direction of grouper controls. type xlsxOutlinePr struct { ApplyStyles *bool `xml:"applyStyles,attr"` SummaryBelow *bool `xml:"summaryBelow,attr"` SummaryRight *bool `xml:"summaryRight,attr"` ShowOutlineSymbols *bool `xml:"showOutlineSymbols,attr"` } // xlsxPageSetUpPr expresses page setup properties of the worksheet. type xlsxPageSetUpPr struct { AutoPageBreaks bool `xml:"autoPageBreaks,attr,omitempty"` FitToPage bool `xml:"fitToPage,attr,omitempty"` } // xlsxCols defines column width and column formatting for one or more columns // of the worksheet. type xlsxCols struct { XMLName xml.Name `xml:"cols"` Col []xlsxCol `xml:"col"` } // xlsxCol directly maps the col (Column Width & Formatting). Defines column // width and column formatting for one or more columns of the worksheet. type xlsxCol struct { BestFit bool `xml:"bestFit,attr,omitempty"` Collapsed bool `xml:"collapsed,attr,omitempty"` CustomWidth bool `xml:"customWidth,attr,omitempty"` Hidden bool `xml:"hidden,attr,omitempty"` Max int `xml:"max,attr"` Min int `xml:"min,attr"` OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` Phonetic bool `xml:"phonetic,attr,omitempty"` Style int `xml:"style,attr,omitempty"` Width *float64 `xml:"width,attr"` } // xlsxDimension directly maps the dimension element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - This element // specifies the used range of the worksheet. It specifies the row and column // bounds of used cells in the worksheet. This is optional and is not // required. Used cells include cells with formulas, text content, and cell // formatting. When an entire column is formatted, only the first cell in that // column is considered used. type xlsxDimension struct { XMLName xml.Name `xml:"dimension"` Ref string `xml:"ref,attr"` } // xlsxSheetData collection represents the cell table itself. This collection // expresses information about each cell, grouped together by rows in the // worksheet. type xlsxSheetData struct { XMLName xml.Name `xml:"sheetData"` Row []xlsxRow `xml:"row"` } // xlsxRow directly maps the row element. The element expresses information // about an entire row of a worksheet, and contains all cell definitions for a // particular row in the worksheet. type xlsxRow struct { C []xlsxC `xml:"c"` R int `xml:"r,attr,omitempty"` Spans string `xml:"spans,attr,omitempty"` S int `xml:"s,attr,omitempty"` CustomFormat bool `xml:"customFormat,attr,omitempty"` Ht *float64 `xml:"ht,attr"` Hidden bool `xml:"hidden,attr,omitempty"` CustomHeight bool `xml:"customHeight,attr,omitempty"` OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` Collapsed bool `xml:"collapsed,attr,omitempty"` ThickTop bool `xml:"thickTop,attr,omitempty"` ThickBot bool `xml:"thickBot,attr,omitempty"` Ph bool `xml:"ph,attr,omitempty"` } // xlsxSortState directly maps the sortState element. This collection // preserves the AutoFilter sort state. type xlsxSortState struct { ColumnSort bool `xml:"columnSort,attr,omitempty"` CaseSensitive bool `xml:"caseSensitive,attr,omitempty"` SortMethod string `xml:"sortMethod,attr,omitempty"` Ref string `xml:"ref,attr"` Content string `xml:",innerxml"` } // xlsxCustomSheetViews directly maps the customSheetViews element. This is a // collection of custom sheet views. type xlsxCustomSheetViews struct { XMLName xml.Name `xml:"customSheetViews"` CustomSheetView []*xlsxCustomSheetView `xml:"customSheetView"` } // xlsxBrk directly maps the row or column break to use when paginating a // worksheet. type xlsxBrk struct { ID int `xml:"id,attr,omitempty"` Min int `xml:"min,attr,omitempty"` Max int `xml:"max,attr,omitempty"` Man bool `xml:"man,attr,omitempty"` Pt bool `xml:"pt,attr,omitempty"` } // xlsxRowBreaks directly maps a collection of the row breaks. type xlsxRowBreaks struct { XMLName xml.Name `xml:"rowBreaks"` xlsxBreaks } // xlsxRowBreaks directly maps a collection of the column breaks. type xlsxColBreaks struct { XMLName xml.Name `xml:"colBreaks"` xlsxBreaks } // xlsxBreaks directly maps a collection of the row or column breaks. type xlsxBreaks struct { Brk []*xlsxBrk `xml:"brk"` Count int `xml:"count,attr,omitempty"` ManualBreakCount int `xml:"manualBreakCount,attr,omitempty"` } // xlsxCustomSheetView directly maps the customSheetView element. type xlsxCustomSheetView struct { Pane *xlsxPane `xml:"pane"` Selection *xlsxSelection `xml:"selection"` RowBreaks *xlsxBreaks `xml:"rowBreaks"` ColBreaks *xlsxBreaks `xml:"colBreaks"` PageMargins *xlsxPageMargins `xml:"pageMargins"` PrintOptions *xlsxPrintOptions `xml:"printOptions"` PageSetup *xlsxPageSetUp `xml:"pageSetup"` HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` AutoFilter *xlsxAutoFilter `xml:"autoFilter"` ExtLst *xlsxExtLst `xml:"extLst"` GUID string `xml:"guid,attr"` Scale int `xml:"scale,attr,omitempty"` ColorID int `xml:"colorId,attr,omitempty"` ShowPageBreaks bool `xml:"showPageBreaks,attr,omitempty"` ShowFormulas bool `xml:"showFormulas,attr,omitempty"` ShowGridLines bool `xml:"showGridLines,attr,omitempty"` ShowRowCol bool `xml:"showRowCol,attr,omitempty"` OutlineSymbols bool `xml:"outlineSymbols,attr,omitempty"` ZeroValues bool `xml:"zeroValues,attr,omitempty"` FitToPage bool `xml:"fitToPage,attr,omitempty"` PrintArea bool `xml:"printArea,attr,omitempty"` Filter bool `xml:"filter,attr,omitempty"` ShowAutoFilter bool `xml:"showAutoFilter,attr,omitempty"` HiddenRows bool `xml:"hiddenRows,attr,omitempty"` HiddenColumns bool `xml:"hiddenColumns,attr,omitempty"` State string `xml:"state,attr,omitempty"` FilterUnique bool `xml:"filterUnique,attr,omitempty"` View string `xml:"view,attr,omitempty"` ShowRuler bool `xml:"showRuler,attr,omitempty"` TopLeftCell string `xml:"topLeftCell,attr,omitempty"` } // xlsxMergeCell directly maps the mergeCell element. A single merged cell. type xlsxMergeCell struct { Ref string `xml:"ref,attr,omitempty"` rect []int } // xlsxMergeCells directly maps the mergeCells element. This collection // expresses all the merged cells in the sheet. type xlsxMergeCells struct { XMLName xml.Name `xml:"mergeCells"` Count int `xml:"count,attr,omitempty"` Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"` } // xlsxDataValidations expresses all data validation information for cells in a // sheet which have data validation features applied. type xlsxDataValidations struct { XMLName xml.Name `xml:"dataValidations"` Count int `xml:"count,attr,omitempty"` DisablePrompts bool `xml:"disablePrompts,attr,omitempty"` XWindow int `xml:"xWindow,attr,omitempty"` YWindow int `xml:"yWindow,attr,omitempty"` DataValidation []*xlsxDataValidation `xml:"dataValidation"` } // xlsxDataValidation directly maps the single item of data validation defined // on a range of the worksheet. type xlsxDataValidation struct { AllowBlank bool `xml:"allowBlank,attr"` Error *string `xml:"error,attr"` ErrorStyle *string `xml:"errorStyle,attr"` ErrorTitle *string `xml:"errorTitle,attr"` Operator string `xml:"operator,attr,omitempty"` Prompt *string `xml:"prompt,attr"` PromptTitle *string `xml:"promptTitle,attr"` ShowDropDown bool `xml:"showDropDown,attr,omitempty"` ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"` ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"` Sqref string `xml:"sqref,attr"` XMSqref string `xml:"sqref,omitempty"` Type string `xml:"type,attr,omitempty"` Formula1 *xlsxInnerXML `xml:"formula1"` Formula2 *xlsxInnerXML `xml:"formula2"` } // xlsxX14DataValidation directly maps the single item of data validation // defined on a extLst element of the worksheet. type xlsxX14DataValidation struct { XMLName xml.Name `xml:"x14:dataValidation"` AllowBlank bool `xml:"allowBlank,attr"` Error *string `xml:"error,attr"` ErrorStyle *string `xml:"errorStyle,attr"` ErrorTitle *string `xml:"errorTitle,attr"` Operator string `xml:"operator,attr,omitempty"` Prompt *string `xml:"prompt,attr"` PromptTitle *string `xml:"promptTitle,attr"` ShowDropDown bool `xml:"showDropDown,attr,omitempty"` ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"` ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"` Sqref string `xml:"sqref,attr"` Type string `xml:"type,attr,omitempty"` Formula1 *xlsxInnerXML `xml:"x14:formula1"` Formula2 *xlsxInnerXML `xml:"x14:formula2"` XMSqref string `xml:"xm:sqref,omitempty"` } // xlsxX14DataValidations expresses all data validation information for cells in // a sheet extLst element which have data validation features applied. type xlsxX14DataValidations struct { XMLName xml.Name `xml:"x14:dataValidations"` XMLNSXM string `xml:"xmlns:xm,attr,omitempty"` Count int `xml:"count,attr,omitempty"` DisablePrompts bool `xml:"disablePrompts,attr,omitempty"` XWindow int `xml:"xWindow,attr,omitempty"` YWindow int `xml:"yWindow,attr,omitempty"` DataValidation []*xlsxX14DataValidation } // xlsxC collection represents a cell in the worksheet. Information about the // cell's location (reference), value, data type, formatting, and formula is // expressed here. // // This simple type is restricted to the values listed in the following table: // // Enumeration Value | Description // ---------------------------+--------------------------------- // b (Boolean) | Cell containing a boolean. // d (Date) | Cell contains a date in the ISO 8601 format. // e (Error) | Cell containing an error. // inlineStr (Inline String) | Cell containing an (inline) rich string, i.e., // | one not in the shared string table. If this // | cell type is used, then the cell value is in // | the is element rather than the v element in // | the cell (c element). // n (Number) | Cell containing a number. // s (Shared String) | Cell containing a shared string. // str (String) | Cell containing a formula string. type xlsxC struct { XMLName xml.Name `xml:"c"` XMLSpace xml.Attr `xml:"space,attr,omitempty"` R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1 S int `xml:"s,attr,omitempty"` // Style reference T string `xml:"t,attr,omitempty"` // Type Cm *uint `xml:"cm,attr"` Vm *uint `xml:"vm,attr"` Ph *bool `xml:"ph,attr"` F *xlsxF `xml:"f"` // Formula V string `xml:"v,omitempty"` // Value IS *xlsxSI `xml:"is"` f string } // xlsxF represents a formula for the cell. The formula expression is // contained in the character node of this element. type xlsxF struct { Content string `xml:",chardata"` T string `xml:"t,attr,omitempty"` // Formula type Aca bool `xml:"aca,attr,omitempty"` Ref string `xml:"ref,attr,omitempty"` // Shared formula ref Dt2D bool `xml:"dt2D,attr,omitempty"` Dtr bool `xml:"dtr,attr,omitempty"` Del1 bool `xml:"del1,attr,omitempty"` Del2 bool `xml:"del2,attr,omitempty"` R1 string `xml:"r1,attr,omitempty"` R2 string `xml:"r2,attr,omitempty"` Ca bool `xml:"ca,attr,omitempty"` Si *int `xml:"si,attr"` // Shared formula index Bx bool `xml:"bx,attr,omitempty"` } // xlsxSheetProtection collection expresses the sheet protection options to // enforce when the sheet is protected. type xlsxSheetProtection struct { XMLName xml.Name `xml:"sheetProtection"` AlgorithmName string `xml:"algorithmName,attr,omitempty"` Password string `xml:"password,attr,omitempty"` HashValue string `xml:"hashValue,attr,omitempty"` SaltValue string `xml:"saltValue,attr,omitempty"` SpinCount int `xml:"spinCount,attr,omitempty"` Sheet bool `xml:"sheet,attr"` Objects bool `xml:"objects,attr"` Scenarios bool `xml:"scenarios,attr"` FormatCells bool `xml:"formatCells,attr"` FormatColumns bool `xml:"formatColumns,attr"` FormatRows bool `xml:"formatRows,attr"` InsertColumns bool `xml:"insertColumns,attr"` InsertRows bool `xml:"insertRows,attr"` InsertHyperlinks bool `xml:"insertHyperlinks,attr"` DeleteColumns bool `xml:"deleteColumns,attr"` DeleteRows bool `xml:"deleteRows,attr"` SelectLockedCells bool `xml:"selectLockedCells,attr"` Sort bool `xml:"sort,attr"` AutoFilter bool `xml:"autoFilter,attr"` PivotTables bool `xml:"pivotTables,attr"` SelectUnlockedCells bool `xml:"selectUnlockedCells,attr"` } // xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic // properties that affect the display of phonetic text for this String Item // (si). Phonetic text is used to give hints as to the pronunciation of an East // Asian language, and the hints are displayed as text within the spreadsheet // cells across the top portion of the cell. Since the phonetic hints are text, // every phonetic hint is expressed as a phonetic run (rPh), and these // properties specify how to display that phonetic run. type xlsxPhoneticPr struct { XMLName xml.Name `xml:"phoneticPr"` Alignment string `xml:"alignment,attr,omitempty"` FontID *int `xml:"fontId,attr"` Type string `xml:"type,attr,omitempty"` } // A Conditional Format is a format, such as cell shading or font color, that a // spreadsheet application can automatically apply to cells if a specified // condition is true. This collection expresses conditional formatting rules // applied to a particular cell or range. type xlsxConditionalFormatting struct { XMLName xml.Name `xml:"conditionalFormatting"` Pivot bool `xml:"pivot,attr,omitempty"` SQRef string `xml:"sqref,attr,omitempty"` CfRule []*xlsxCfRule `xml:"cfRule"` } // xlsxCfRule (Conditional Formatting Rule) represents a description of a // conditional formatting rule. type xlsxCfRule struct { Type string `xml:"type,attr,omitempty"` DxfID *int `xml:"dxfId,attr"` Priority int `xml:"priority,attr,omitempty"` StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"` AboveAverage *bool `xml:"aboveAverage,attr"` Percent bool `xml:"percent,attr,omitempty"` Bottom bool `xml:"bottom,attr,omitempty"` Operator string `xml:"operator,attr,omitempty"` Text string `xml:"text,attr,omitempty"` TimePeriod string `xml:"timePeriod,attr,omitempty"` Rank int `xml:"rank,attr,omitempty"` StdDev int `xml:"stdDev,attr,omitempty"` EqualAverage bool `xml:"equalAverage,attr,omitempty"` Formula []string `xml:"formula,omitempty"` ColorScale *xlsxColorScale `xml:"colorScale"` DataBar *xlsxDataBar `xml:"dataBar"` IconSet *xlsxIconSet `xml:"iconSet"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxColorScale (Color Scale) describes a gradated color scale in this // conditional formatting rule. type xlsxColorScale struct { Cfvo []*xlsxCfvo `xml:"cfvo"` Color []*xlsxColor `xml:"color"` } // dataBar (Data Bar) describes a data bar conditional formatting rule. type xlsxDataBar struct { MaxLength int `xml:"maxLength,attr,omitempty"` MinLength int `xml:"minLength,attr,omitempty"` ShowValue *bool `xml:"showValue,attr"` Cfvo []*xlsxCfvo `xml:"cfvo"` Color []*xlsxColor `xml:"color"` } // xlsxIconSet (Icon Set) describes an icon set conditional formatting rule. type xlsxIconSet struct { Cfvo []*xlsxCfvo `xml:"cfvo"` IconSet string `xml:"iconSet,attr,omitempty"` ShowValue *bool `xml:"showValue,attr"` Percent bool `xml:"percent,attr,omitempty"` Reverse bool `xml:"reverse,attr,omitempty"` } // cfvo (Conditional Format Value Object) describes the values of the // interpolation points in a gradient scale. type xlsxCfvo struct { Gte bool `xml:"gte,attr,omitempty"` Type string `xml:"type,attr,omitempty"` Val string `xml:"val,attr,omitempty"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxHyperlinks directly maps the hyperlinks element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - A hyperlink can // be stored in a package as a relationship. Hyperlinks shall be identified by // containing a target which specifies the destination of the given hyperlink. type xlsxHyperlinks struct { XMLName xml.Name `xml:"hyperlinks"` Hyperlink []xlsxHyperlink `xml:"hyperlink"` } // xlsxHyperlink directly maps the hyperlink element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main type xlsxHyperlink struct { Ref string `xml:"ref,attr"` Location string `xml:"location,attr,omitempty"` Display string `xml:"display,attr,omitempty"` Tooltip string `xml:"tooltip,attr,omitempty"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxTableParts directly maps the tableParts element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - The table element // has several attributes applied to identify the table and the data range it // covers. The table id attribute needs to be unique across all table parts, the // same goes for the name and displayName. The displayName has the further // restriction that it must be unique across all defined names in the workbook. // Later on we will see that you can define names for many elements, such as // cells or formulas. The name value is used for the object model in Microsoft // Office Excel. The displayName is used for references in formulas. The ref // attribute is used to identify the cell range that the table covers. This // includes not only the table data, but also the table header containing column // names. // To add columns to your table you add new tableColumn elements to the // tableColumns container. Similar to the shared string table the collection // keeps a count attribute identifying the number of columns. Besides the table // definition in the table part there is also the need to identify which tables // are displayed in the worksheet. The worksheet part has a separate element // tableParts to store this information. Each table part is referenced through // the relationship ID and again a count of the number of table parts is // maintained. The following markup sample is taken from the documents // accompanying this book. The sheet data element has been removed to reduce the // size of the sample. To reference the table, just add the tableParts element, // of course after having created and stored the table part. For example: // // // ... // // // // type xlsxTableParts struct { XMLName xml.Name `xml:"tableParts"` Count int `xml:"count,attr,omitempty"` TableParts []*xlsxTablePart `xml:"tablePart"` } // xlsxTablePart directly maps the tablePart element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main type xlsxTablePart struct { RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxPicture directly maps the picture element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Background sheet // image. For example: // // type xlsxPicture struct { XMLName xml.Name `xml:"picture"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxIgnoredError specifies a single ignored error for a range of cells. type xlsxIgnoredError struct { XMLName xml.Name `xml:"ignoredError"` Sqref string `xml:"sqref,attr"` EvalError bool `xml:"evalError,attr,omitempty"` TwoDigitTextYear bool `xml:"twoDigitTextYear,attr,omitempty"` NumberStoredAsText bool `xml:"numberStoredAsText,attr,omitempty"` Formula bool `xml:"formula,attr,omitempty"` FormulaRange bool `xml:"formulaRange,attr,omitempty"` UnlockedFormula bool `xml:"unlockedFormula,attr,omitempty"` EmptyCellReference bool `xml:"emptyCellReference,attr,omitempty"` ListDataValidation bool `xml:"listDataValidation,attr,omitempty"` CalculatedColumn bool `xml:"calculatedColumn,attr,omitempty"` } // xlsxIgnoredErrors specifies a collection of ignored errors, by cell range. type xlsxIgnoredErrors struct { XMLName xml.Name `xml:"ignoredErrors"` IgnoredError []xlsxIgnoredError `xml:"ignoredError"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxLegacyDrawing directly maps the legacyDrawing element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main - A comment is a // rich text note that is attached to, and associated with, a cell, separate // from other cell content. Comment content is stored separate from the cell, // and is displayed in a drawing object (like a text box) that is separate from, // but associated with, a cell. Comments are used as reminders, such as noting // how a complex formula works, or to provide feedback to other users. Comments // can also be used to explain assumptions made in a formula or to call out // something special about the cell. type xlsxLegacyDrawing struct { XMLName xml.Name `xml:"legacyDrawing"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // xlsxLegacyDrawingHF specifies the explicit relationship to the part // containing the VML defining pictures rendered in the header / footer of the // sheet. type xlsxLegacyDrawingHF struct { XMLName xml.Name `xml:"legacyDrawingHF"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } // decodeX14SparklineGroups directly maps the sparklineGroups element. type decodeX14SparklineGroups struct { XMLName xml.Name `xml:"sparklineGroups"` XMLNSXM string `xml:"xmlns:xm,attr"` Content string `xml:",innerxml"` } // decodeX14ConditionalFormattingExt directly maps the ext element. type decodeX14ConditionalFormattingExt struct { XMLName xml.Name `xml:"ext"` ID string `xml:"id"` } // decodeX14ConditionalFormattings directly maps the conditionalFormattings // element. type decodeX14ConditionalFormattings struct { XMLName xml.Name `xml:"conditionalFormattings"` XMLNSXM string `xml:"xmlns:xm,attr"` Content string `xml:",innerxml"` } // decodeX14ConditionalFormattingRules directly maps the conditionalFormattings // element. type decodeX14ConditionalFormattingRules struct { XMLName xml.Name `xml:"conditionalFormattings"` XMLNSXM string `xml:"xmlns:xm,attr"` CondFmt []decodeX14ConditionalFormatting `xml:"conditionalFormatting"` } // decodeX14ConditionalFormatting directly maps the conditionalFormatting // element. type decodeX14ConditionalFormatting struct { XMLName xml.Name `xml:"conditionalFormatting"` Pivot bool `xml:"pivot,attr,omitempty"` CfRule []*decodeX14CfRule `xml:"cfRule"` Sqref string `xml:"sqref,omitempty"` ExtLst *xlsxExtLst `xml:"x14:extLst"` } // decodeX14CfRule directly maps the cfRule element. type decodeX14CfRule struct { XMLName xml.Name `xml:"cfRule"` Type string `xml:"type,attr,omitempty"` Priority int `xml:"priority,attr,omitempty"` StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"` AboveAverage *bool `xml:"aboveAverage,attr"` Percent bool `xml:"percent,attr,omitempty"` Bottom bool `xml:"bottom,attr,omitempty"` Operator string `xml:"operator,attr,omitempty"` Text string `xml:"text,attr,omitempty"` TimePeriod string `xml:"timePeriod,attr,omitempty"` Rank int `xml:"rank,attr,omitempty"` StdDev int `xml:"stdDev,attr,omitempty"` EqualAverage bool `xml:"equalAverage,attr,omitempty"` ActivePresent bool `xml:"activePresent,attr,omitempty"` ID string `xml:"id,attr,omitempty"` F []string `xml:"f"` ColorScale *xlsxInnerXML `xml:"colorScale"` DataBar *decodeX14DataBar `xml:"dataBar"` IconSet *decodeX14IconSet `xml:"iconSet"` Dxf *xlsxInnerXML `xml:"dxf"` ExtLst *xlsxExtLst `xml:"extLst"` } // decodeX14DataBar directly maps the dataBar element. type decodeX14DataBar struct { XMLName xml.Name `xml:"dataBar"` MaxLength int `xml:"maxLength,attr"` MinLength int `xml:"minLength,attr"` Border bool `xml:"border,attr,omitempty"` Gradient *bool `xml:"gradient,attr"` ShowValue bool `xml:"showValue,attr,omitempty"` Direction string `xml:"direction,attr,omitempty"` Cfvo []*xlsxCfvo `xml:"cfvo"` BorderColor *xlsxColor `xml:"borderColor"` NegativeFillColor *xlsxColor `xml:"negativeFillColor"` AxisColor *xlsxColor `xml:"axisColor"` } // decodeX14IconSet directly maps the iconSet element. type decodeX14IconSet struct { XMLName xml.Name `xml:"iconSet"` IconSet string `xml:"iconSet,attr,omitempty"` ShowValue *bool `xml:"showValue,attr"` Percent *bool `xml:"percent,attr"` Reverse bool `xml:"reverse,attr,omitempty"` Custom bool `xml:"custom,attr,omitempty"` Cfvo []*decodeX14Cfvo `xml:"cfvo"` CfIcon []*xlsxInnerXML `xml:"cfIcon"` } // decodeX14Cfvo directly maps the cfvo element. type decodeX14Cfvo struct { XMLName xml.Name `xml:"cfvo"` Type string `xml:"type,attr"` Gte *bool `xml:"gte,attr"` F string `xml:"f"` ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxX14ConditionalFormattings directly maps the x14:conditionalFormattings // element. type xlsxX14ConditionalFormattings struct { XMLName xml.Name `xml:"x14:conditionalFormattings"` Content string `xml:",innerxml"` } // xlsxX14ConditionalFormatting directly maps the x14:conditionalFormatting // element. type xlsxX14ConditionalFormatting struct { XMLName xml.Name `xml:"x14:conditionalFormatting"` XMLNSXM string `xml:"xmlns:xm,attr"` Pivot bool `xml:"pivot,attr,omitempty"` CfRule []*xlsxX14CfRule `xml:"x14:cfRule"` Sqref string `xml:"xm:sqref,omitempty"` ExtLst *xlsxExtLst `xml:"x14:extLst"` } // xlsxX14CfRule directly maps the x14:cfRule element. type xlsxX14CfRule struct { Type string `xml:"type,attr,omitempty"` Priority int `xml:"priority,attr,omitempty"` StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"` AboveAverage *bool `xml:"aboveAverage,attr"` Percent bool `xml:"percent,attr,omitempty"` Bottom bool `xml:"bottom,attr,omitempty"` Operator string `xml:"operator,attr,omitempty"` Text string `xml:"text,attr,omitempty"` TimePeriod string `xml:"timePeriod,attr,omitempty"` Rank int `xml:"rank,attr,omitempty"` StdDev int `xml:"stdDev,attr,omitempty"` EqualAverage bool `xml:"equalAverage,attr,omitempty"` ActivePresent bool `xml:"activePresent,attr,omitempty"` ID string `xml:"id,attr,omitempty"` F []string `xml:"xm:f"` ColorScale *xlsxInnerXML `xml:"x14:colorScale"` DataBar *xlsx14DataBar `xml:"x14:dataBar"` IconSet *xlsx14IconSet `xml:"x14:iconSet"` Dxf *xlsxInnerXML `xml:"x14:dxf"` ExtLst *xlsxExtLst `xml:"x14:extLst"` } // xlsx14DataBar directly maps the x14:dataBar element. type xlsx14DataBar struct { MaxLength int `xml:"maxLength,attr"` MinLength int `xml:"minLength,attr"` Border bool `xml:"border,attr"` Gradient bool `xml:"gradient,attr"` ShowValue bool `xml:"showValue,attr,omitempty"` Direction string `xml:"direction,attr,omitempty"` Cfvo []*xlsxCfvo `xml:"x14:cfvo"` BorderColor *xlsxColor `xml:"x14:borderColor"` NegativeFillColor *xlsxColor `xml:"x14:negativeFillColor"` AxisColor *xlsxColor `xml:"x14:axisColor"` } // xlsx14IconSet directly maps the x14:iconSet element. This element specifies // the properties of a conditional formatting rule that uses an icon set. type xlsx14IconSet struct { IconSet string `xml:"iconSet,attr,omitempty"` ShowValue *bool `xml:"showValue,attr"` Percent *bool `xml:"percent,attr"` Reverse bool `xml:"reverse,attr,omitempty"` Custom bool `xml:"custom,attr,omitempty"` Cfvo []*xlsx14Cfvo `xml:"x14:cfvo"` CfIcon []*xlsxInnerXML `xml:"x14:cfIcon"` } // xlsx14IconSet directly maps the x14:cfvo element. This element specifies how // to calculate a value from the range of cells to which a conditional // formatting rule applies. type xlsx14Cfvo struct { Type string `xml:"type,attr"` Gte *bool `xml:"gte,attr"` F string `xml:"xm:f"` ExtLst *xlsxExtLst `xml:"x14:extLst"` } // xlsxX14SparklineGroups directly maps the x14:sparklineGroups element. type xlsxX14SparklineGroups struct { XMLName xml.Name `xml:"x14:sparklineGroups"` XMLNSXM string `xml:"xmlns:xm,attr"` SparklineGroups []*xlsxX14SparklineGroup `xml:"x14:sparklineGroup"` Content string `xml:",innerxml"` } // xlsxX14SparklineGroup directly maps the x14:sparklineGroup element. type xlsxX14SparklineGroup struct { XMLName xml.Name `xml:"x14:sparklineGroup"` ManualMax int `xml:"manualMax,attr,omitempty"` ManualMin int `xml:"manualMin,attr,omitempty"` LineWeight float64 `xml:"lineWeight,attr,omitempty"` Type string `xml:"type,attr,omitempty"` DateAxis bool `xml:"dateAxis,attr,omitempty"` DisplayEmptyCellsAs string `xml:"displayEmptyCellsAs,attr,omitempty"` Markers bool `xml:"markers,attr,omitempty"` High bool `xml:"high,attr,omitempty"` Low bool `xml:"low,attr,omitempty"` First bool `xml:"first,attr,omitempty"` Last bool `xml:"last,attr,omitempty"` Negative bool `xml:"negative,attr,omitempty"` DisplayXAxis bool `xml:"displayXAxis,attr,omitempty"` DisplayHidden bool `xml:"displayHidden,attr,omitempty"` MinAxisType string `xml:"minAxisType,attr,omitempty"` MaxAxisType string `xml:"maxAxisType,attr,omitempty"` RightToLeft bool `xml:"rightToLeft,attr,omitempty"` ColorSeries *xlsxColor `xml:"x14:colorSeries"` ColorNegative *xlsxColor `xml:"x14:colorNegative"` ColorAxis *xlsxColor `xml:"x14:colorAxis"` ColorMarkers *xlsxColor `xml:"x14:colorMarkers"` ColorFirst *xlsxColor `xml:"x14:colorFirst"` ColorLast *xlsxColor `xml:"x14:colorLast"` ColorHigh *xlsxColor `xml:"x14:colorHigh"` ColorLow *xlsxColor `xml:"x14:colorLow"` Sparklines xlsxX14Sparklines `xml:"x14:sparklines"` } // xlsxX14Sparklines directly maps the x14:sparklines element. type xlsxX14Sparklines struct { Sparkline []*xlsxX14Sparkline `xml:"x14:sparkline"` } // xlsxX14Sparkline directly maps the x14:sparkline element. type xlsxX14Sparkline struct { F string `xml:"xm:f"` Sqref string `xml:"xm:sqref"` } // DataValidation directly maps the settings of the data validation rule. type DataValidation struct { AllowBlank bool Error *string ErrorStyle *string ErrorTitle *string Operator string Prompt *string PromptTitle *string ShowDropDown bool ShowErrorMessage bool ShowInputMessage bool Sqref string Type string Formula1 string Formula2 string } // SparklineOptions directly maps the settings of the sparkline. type SparklineOptions struct { Location []string Range []string Max int CustMax int Min int CustMin int Type string Weight float64 DateAxis bool Markers bool High bool Low bool First bool Last bool Negative bool Axis bool Hidden bool Reverse bool Style int SeriesColor string NegativeColor string MarkersColor string FirstColor string LastColor string HightColor string LowColor string EmptyCells string } // Selection directly maps the settings of the worksheet selection. type Selection struct { SQRef string ActiveCell string Pane string } // Panes directly maps the settings of the panes. type Panes struct { Freeze bool Split bool XSplit int YSplit int TopLeftCell string ActivePane string Selection []Selection } // ConditionalFormatOptions directly maps the conditional format settings of the cells. type ConditionalFormatOptions struct { Type string AboveAverage bool Percent bool Format *int Criteria string Value string MinType string MidType string MaxType string MinValue string MidValue string MaxValue string MinColor string MidColor string MaxColor string BarColor string BarBorderColor string BarDirection string BarOnly bool BarSolid bool IconStyle string ReverseIcons bool IconsOnly bool StopIfTrue bool } // SheetProtectionOptions directly maps the settings of worksheet protection. type SheetProtectionOptions struct { AlgorithmName string AutoFilter bool DeleteColumns bool DeleteRows bool EditObjects bool EditScenarios bool FormatCells bool FormatColumns bool FormatRows bool InsertColumns bool InsertHyperlinks bool InsertRows bool Password string PivotTables bool SelectLockedCells bool SelectUnlockedCells bool Sort bool } // HeaderFooterOptions directly maps the settings of header and footer. type HeaderFooterOptions struct { AlignWithMargins *bool DifferentFirst bool DifferentOddEven bool ScaleWithDoc *bool OddHeader string OddFooter string EvenHeader string EvenFooter string FirstHeader string FirstFooter string } // PageLayoutMarginsOptions directly maps the settings of page layout margins. type PageLayoutMarginsOptions struct { Bottom *float64 Footer *float64 Header *float64 Left *float64 Right *float64 Top *float64 Horizontally *bool Vertically *bool } // PageLayoutOptions directly maps the settings of page layout. type PageLayoutOptions struct { // Size defines the paper size of the worksheet. Size *int // Orientation defines the orientation of page layout for a worksheet. Orientation *string // FirstPageNumber specified the first printed page number. If no value is // specified, then 'automatic' is assumed. FirstPageNumber *uint // AdjustTo defines the print scaling. This attribute is restricted to // value ranging from 10 (10%) to 400 (400%). This setting is overridden // when fitToWidth and/or fitToHeight are in use. AdjustTo *uint // FitToHeight specified the number of vertical pages to fit on. FitToHeight *int // FitToWidth specified the number of horizontal pages to fit on. FitToWidth *int // BlackAndWhite specified print black and white. BlackAndWhite *bool // PageOrder specifies the ordering of multiple pages. Values // accepted: overThenDown and downThenOver PageOrder *string } // ViewOptions directly maps the settings of sheet view. type ViewOptions struct { // DefaultGridColor indicating that the consuming application should use // the default grid lines color(system dependent). Overrides any color // specified in colorId. DefaultGridColor *bool // RightToLeft indicating whether the sheet is in 'right to left' display // mode. When in this mode, Column A is on the far right, Column B; is one // column left of Column A, and so on. Also, information in cells is // displayed in the Right to Left format. RightToLeft *bool // ShowFormulas indicating whether this sheet should display formulas. ShowFormulas *bool // ShowGridLines indicating whether this sheet should display grid lines. ShowGridLines *bool // ShowRowColHeaders indicating whether the sheet should display row and // column headings. ShowRowColHeaders *bool // ShowRuler indicating this sheet should display ruler. ShowRuler *bool // ShowZeros indicating whether to "show a zero in cells that have zero // value". When using a formula to reference another cell which is empty, // the referenced value becomes 0 when the flag is true. (Default setting // is true.) ShowZeros *bool // TopLeftCell specifies a location of the top left visible cell Location // of the top left visible cell in the bottom right pane (when in // Left-to-Right mode). TopLeftCell *string // View indicating how sheet is displayed, by default it uses empty string // available options: normal, pageLayout, pageBreakPreview View *string // ZoomScale specifies a window zoom magnification for current view // representing percent values. This attribute is restricted to values // ranging from 10 to 400. Horizontal & Vertical scale together. ZoomScale *float64 } // SheetPropsOptions provides a function to set worksheet properties. There 4 // kinds of presets "Custom Scaling Options" in the spreadsheet applications, if // you need to set those kind of scaling options, please using the // "SetSheetProps" and "SetPageLayout" functions to approach these 4 scaling // options: // // 1. No Scaling (Print sheets at their actual size): // // disable := false // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // FitToPage: &disable, // }); err != nil { // fmt.Println(err) // } // // 2. Fit Sheet on One Page (Shrink the printout so that it fits on one page): // // enable := true // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // FitToPage: &enable, // }); err != nil { // fmt.Println(err) // } // // 3. Fit All Columns on One Page (Shrink the printout so that it is one page // wide): // // enable, zero := true, 0 // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // FitToPage: &enable, // }); err != nil { // fmt.Println(err) // } // if err := f.SetPageLayout("Sheet1", &excelize.PageLayoutOptions{ // FitToHeight: &zero, // }); err != nil { // fmt.Println(err) // } // // 4. Fit All Rows on One Page (Shrink the printout so that it is one page // high): // // enable, zero := true, 0 // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // FitToPage: &enable, // }); err != nil { // fmt.Println(err) // } // if err := f.SetPageLayout("Sheet1", &excelize.PageLayoutOptions{ // FitToWidth: &zero, // }); err != nil { // fmt.Println(err) // } type SheetPropsOptions struct { // Specifies a stable name of the sheet, which should not change over time, // and does not change from user input. This name should be used by code // to reference a particular sheet. CodeName *string // EnableFormatConditionsCalculation indicating whether the conditional // formatting calculations shall be evaluated. If set to false, then the // min/max values of color scales or data bars or threshold values in Top N // rules shall not be updated. Essentially the conditional // formatting "calc" is off. EnableFormatConditionsCalculation *bool // Published indicating whether the worksheet is published. Published *bool // AutoPageBreaks indicating whether the sheet displays Automatic Page // Breaks. AutoPageBreaks *bool // FitToPage indicating whether the Fit to Page print option is enabled. FitToPage *bool // TabColorIndexed represents the indexed color value. TabColorIndexed *int // TabColorRGB represents the standard Alpha Red Green Blue color value. TabColorRGB *string // TabColorTheme represents the zero-based index into the collection, // referencing a particular value expressed in the Theme part. TabColorTheme *int // TabColorTint specifies the tint value applied to the color. TabColorTint *float64 // OutlineSummaryBelow indicating whether summary rows appear below detail // in an outline, when applying an outline. OutlineSummaryBelow *bool // OutlineSummaryRight indicating whether summary columns appear to the // right of detail in an outline, when applying an outline. OutlineSummaryRight *bool // BaseColWidth specifies the number of characters of the maximum digit // width of the normal style's font. This value does not include margin // padding or extra padding for grid lines. It is only the number of // characters. BaseColWidth *uint8 // DefaultColWidth specifies the default column width measured as the // number of characters of the maximum digit width of the normal style's // font. DefaultColWidth *float64 // DefaultRowHeight specifies the default row height measured in point // size. Optimization so we don't have to write the height on all rows. // This can be written out if most rows have custom height, to achieve the // optimization. DefaultRowHeight *float64 // CustomHeight specifies the custom height. CustomHeight *bool // ZeroHeight specifies if rows are hidden. ZeroHeight *bool // ThickTop specifies if rows have a thick top border by default. ThickTop *bool // ThickBottom specifies if rows have a thick bottom border by default. ThickBottom *bool }